mirror of
https://github.com/ansible/awx.git
synced 2026-03-29 06:45:09 -02:30
Merge branch 'devel' into 169-v1
This commit is contained in:
@@ -71,16 +71,13 @@ function ComponentsStrings (BaseString) {
|
||||
INVENTORY_SCRIPTS: t.s('Inventory Scripts'),
|
||||
NOTIFICATIONS: t.s('Notifications'),
|
||||
MANAGEMENT_JOBS: t.s('Management Jobs'),
|
||||
INSTANCES: t.s('Instances'),
|
||||
INSTANCE_GROUPS: t.s('Instance Groups'),
|
||||
SETTINGS: t.s('Settings'),
|
||||
FOOTER_ABOUT: t.s('About'),
|
||||
FOOTER_COPYRIGHT: t.s('Copyright © 2017 Red Hat, Inc.')
|
||||
};
|
||||
|
||||
ns.capacityBar = {
|
||||
IS_OFFLINE: t.s('Unavailable to run jobs.')
|
||||
};
|
||||
|
||||
ns.relaunch = {
|
||||
DEFAULT: t.s('Relaunch using the same parameters'),
|
||||
HOSTS: t.s('Relaunch using host parameters'),
|
||||
@@ -90,7 +87,7 @@ function ComponentsStrings (BaseString) {
|
||||
};
|
||||
|
||||
ns.list = {
|
||||
DEFAULT_EMPTY_LIST: t.s('List is empty.')
|
||||
DEFAULT_EMPTY_LIST: t.s('Please add items to this list.')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import inputLookup from '~components/input/lookup.directive';
|
||||
import inputMessage from '~components/input/message.directive';
|
||||
import inputSecret from '~components/input/secret.directive';
|
||||
import inputSelect from '~components/input/select.directive';
|
||||
import inputSlider from '~components/input/slider.directive';
|
||||
import inputText from '~components/input/text.directive';
|
||||
import inputTextarea from '~components/input/textarea.directive';
|
||||
import inputTextareaSecret from '~components/input/textarea-secret.directive';
|
||||
@@ -54,6 +55,7 @@ angular
|
||||
.directive('atInputMessage', inputMessage)
|
||||
.directive('atInputSecret', inputSecret)
|
||||
.directive('atInputSelect', inputSelect)
|
||||
.directive('atInputSlider', inputSlider)
|
||||
.directive('atInputText', inputText)
|
||||
.directive('atInputTextarea', inputTextarea)
|
||||
.directive('atInputTextareaSecret', inputTextareaSecret)
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
}
|
||||
|
||||
.at-InputMessage--rejected {
|
||||
font-size: @at-font-size-help-text;
|
||||
font-size: @at-font-size-help-text;
|
||||
color: @at-color-error;
|
||||
margin: @at-margin-input-message 0 0 0;
|
||||
padding: 0;
|
||||
@@ -182,7 +182,7 @@
|
||||
|
||||
& > i {
|
||||
font-size: @at-font-size-button;
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
top: @at-height-input / 3;
|
||||
@@ -218,3 +218,47 @@
|
||||
min-height: @at-height-textarea;
|
||||
padding: 6px @at-padding-input 0 @at-padding-input;
|
||||
}
|
||||
|
||||
.at-InputSlider {
|
||||
display: flex;
|
||||
padding: 5px 0;
|
||||
|
||||
p {
|
||||
color: @at-color-form-label;
|
||||
font-size: @at-font-size-help-text;
|
||||
font-weight: @at-font-weight-body;
|
||||
margin: 0 0 0 10px;
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
height: 20px;
|
||||
border-right: 1px solid @at-color-input-slider-track;
|
||||
border-left: 1px solid @at-color-input-slider-track;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
background: @at-color-input-slider-track;
|
||||
cursor: pointer;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
background: @at-color-input-slider-thumb;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
height: 16px;
|
||||
margin-top: -7px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,10 @@ function AtInputLookupController (baseInputController, $q, $state) {
|
||||
|
||||
vm.searchAfterDebounce();
|
||||
};
|
||||
|
||||
vm.removeTag = (tagToRemove) => {
|
||||
_.remove(scope.state._value, (tag) => tag === tagToRemove);
|
||||
};
|
||||
}
|
||||
|
||||
AtInputLookupController.$inject = [
|
||||
|
||||
@@ -11,17 +11,30 @@
|
||||
</button>
|
||||
</span>
|
||||
<input type="text"
|
||||
class="form-control at-Input"
|
||||
ng-class="{ 'at-Input--rejected': state._rejected }"
|
||||
ng-model="state._displayValue"
|
||||
ng-attr-tabindex="{{ tab || undefined }}"
|
||||
ng-attr-placeholder="{{::state._placeholder || undefined }}"
|
||||
ng-change="vm.searchOnInput()"
|
||||
ng-disabled="state._disabled || form.disabled" />
|
||||
class="form-control at-Input"
|
||||
ng-class="{ 'at-Input--rejected': state._rejected }"
|
||||
ng-model="state._displayValue"
|
||||
ng-attr-tabindex="{{ tab || undefined }}"
|
||||
ng-attr-placeholder="{{::state._placeholder || undefined }}"
|
||||
ng-change="vm.searchOnInput()"
|
||||
ng-hide="state._lookupTags"
|
||||
ng-disabled="state._disabled || form.disabled">
|
||||
|
||||
<span class="form-control Form-textInput Form-textInput--variableHeight LabelList-lookupTags"
|
||||
ng-if="state._lookupTags">
|
||||
<div class="LabelList-tagContainer" ng-repeat="tag in state._value track by $index">
|
||||
<div class="LabelList-deleteContainer" ng-click="vm.removeTag(tag)">
|
||||
<i class="fa fa-times LabelList-tagDelete"></i>
|
||||
</div>
|
||||
<div class="LabelList-tag LabelList-tag--deletable">
|
||||
<span ng-if="tag.hostname" class="LabelList-name">{{ tag.hostname }}</span>
|
||||
<span ng-if="!tag.hostname" class="LabelList-name">{{ tag }}</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<at-input-message></at-input-message>
|
||||
</div>
|
||||
|
||||
<div ui-view="{{ state._resource }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
38
awx/ui/client/lib/components/input/slider.directive.js
Normal file
38
awx/ui/client/lib/components/input/slider.directive.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const templateUrl = require('~components/input/slider.partial.html');
|
||||
|
||||
function atInputSliderLink (scope, element, attrs, controllers) {
|
||||
const [formController, inputController] = controllers;
|
||||
|
||||
inputController.init(scope, element, formController);
|
||||
}
|
||||
|
||||
function atInputSliderController (baseInputController) {
|
||||
const vm = this || {};
|
||||
|
||||
vm.init = (_scope_, _element_, form) => {
|
||||
baseInputController.call(vm, 'input', _scope_, _element_, form);
|
||||
|
||||
vm.check();
|
||||
};
|
||||
}
|
||||
|
||||
atInputSliderController.$inject = ['BaseInputController'];
|
||||
|
||||
function atInputSlider () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: ['^^atForm', 'atInputSlider'],
|
||||
replace: true,
|
||||
templateUrl,
|
||||
controller: atInputSliderController,
|
||||
controllerAs: 'vm',
|
||||
link: atInputSliderLink,
|
||||
scope: {
|
||||
state: '=?',
|
||||
col: '@',
|
||||
tab: '@'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default atInputSlider;
|
||||
13
awx/ui/client/lib/components/input/slider.partial.html
Normal file
13
awx/ui/client/lib/components/input/slider.partial.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="col-sm-{{::col}} at-InputContainer">
|
||||
<div class="form-group at-u-flat">
|
||||
<at-input-label></at-input-label>
|
||||
<div class="at-InputSlider">
|
||||
<input type="range"
|
||||
ng-model="state._value"
|
||||
min="0"
|
||||
max="100"
|
||||
ng-change="vm.slide(state._value)"/>
|
||||
<p>{{ state._value }}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,12 +86,35 @@
|
||||
border-top: @at-border-default-width solid @at-color-list-border;
|
||||
}
|
||||
|
||||
.at-Row--rowLayout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.at-RowItem {
|
||||
margin-right: @at-space-4x;
|
||||
|
||||
&-label {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.at-RowStatus {
|
||||
align-self: flex-start;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.at-Row-firstColumn {
|
||||
margin-right: @at-space-4x;
|
||||
}
|
||||
|
||||
.at-Row-actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.at-Row-items {
|
||||
align-self: flex-start;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.at-RowItem {
|
||||
@@ -101,10 +124,19 @@
|
||||
}
|
||||
|
||||
.at-RowItem--isHeader {
|
||||
color: @at-color-body-text;
|
||||
margin-bottom: @at-margin-bottom-list-header;
|
||||
line-height: @at-line-height-list-row-item-header;
|
||||
}
|
||||
|
||||
.at-RowItem--isHeaderLink {
|
||||
color: @at-blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
.at-RowItem--isHeaderLink:hover {
|
||||
color: @at-blue-hover;
|
||||
}
|
||||
|
||||
.at-RowItem--labels {
|
||||
line-height: @at-line-height-list-row-item-labels;
|
||||
}
|
||||
@@ -146,8 +178,26 @@
|
||||
|
||||
.at-RowItem-label {
|
||||
text-transform: uppercase;
|
||||
width: auto;
|
||||
width: @at-width-list-row-item-label;
|
||||
color: @at-color-list-row-item-label;
|
||||
font-size: @at-font-size;
|
||||
}
|
||||
|
||||
.at-RowItem-value {
|
||||
font-size: @at-font-size-3x;
|
||||
}
|
||||
|
||||
.at-RowItem-badge {
|
||||
background-color: @at-gray-848992;
|
||||
border-radius: @at-border-radius;
|
||||
color: @at-white;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
height: 14px;
|
||||
line-height: 10px;
|
||||
margin: 0 10px;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
|
||||
.at-RowAction {
|
||||
@@ -180,6 +230,11 @@
|
||||
background-color: @at-color-list-row-action-hover-danger;
|
||||
}
|
||||
|
||||
.at-Row .at-Row-checkbox {
|
||||
align-self: start;
|
||||
margin: 2px 20px 0 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: @at-breakpoint-compact-list) {
|
||||
.at-Row-actions {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -7,10 +7,13 @@ function atRowItem () {
|
||||
transclude: true,
|
||||
templateUrl,
|
||||
scope: {
|
||||
badge: '@',
|
||||
headerValue: '@',
|
||||
headerLink: '@',
|
||||
headerTag: '@',
|
||||
labelValue: '@',
|
||||
labelLink: '@',
|
||||
labelState: '@',
|
||||
value: '@',
|
||||
valueLink: '@',
|
||||
smartStatus: '=?',
|
||||
|
||||
@@ -9,13 +9,19 @@
|
||||
<div class="at-RowItem-tag at-RowItem-tag--header" ng-if="headerTag">
|
||||
{{ headerTag }}
|
||||
</div>
|
||||
<div class="at-RowItem-label" ng-if="labelValue">
|
||||
<div class="at-RowItem-label" ng-if="labelValue && labelLink">
|
||||
<a ng-href="{{ labelLink }}">{{ labelValue }}</a>
|
||||
</div>
|
||||
<div class="at-RowItem-label" ng-if="labelValue && !labelLink && !labelState">
|
||||
{{ labelValue }}
|
||||
</div>
|
||||
<div class="at-RowItem-label" ng-if="labelValue && labelState">
|
||||
<a ui-sref="{{ labelState }}" ui-sref-opts="{reload: true, notify: true}">{{ labelValue }}</a>
|
||||
</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"
|
||||
<div class="at-RowItem-value" ng-class="{'at-RowItem-badge': badge}" ng-if="value && !valueLink"
|
||||
ng-bind-html="value">
|
||||
</div>
|
||||
<aw-smart-status jobs="smartStatus.summary_fields.recent_jobs"
|
||||
@@ -35,4 +41,4 @@
|
||||
{{ tag.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,3 +26,7 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.at-TabGroup + .at-Panel-body {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<button class="btn at-ButtonHollow--default at-Tab"
|
||||
ng-attr-disabled="{{ state._disabled || undefined }}"
|
||||
ng-class="{ 'at-Tab--active': state._active, 'at-Tab--disabled': state._disabled }"
|
||||
ng-hide="{{ state._hide }}"
|
||||
ng-click="vm.go()">
|
||||
<ng-transclude></ng-transclude>
|
||||
</button>
|
||||
|
||||
@@ -129,6 +129,10 @@ function httpPost (config = {}) {
|
||||
data: config.data
|
||||
};
|
||||
|
||||
if (config.url) {
|
||||
req.url = `${this.path}${config.url}`;
|
||||
}
|
||||
|
||||
return $http(req)
|
||||
.then(res => {
|
||||
this.model.GET = res.data;
|
||||
@@ -323,7 +327,7 @@ function has (method, keys) {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
|
||||
function extend (method, related) {
|
||||
function extend (method, related, config = {}) {
|
||||
if (!related) {
|
||||
related = method;
|
||||
method = 'GET';
|
||||
@@ -337,6 +341,8 @@ function extend (method, related) {
|
||||
url: this.get(`related.${related}`)
|
||||
};
|
||||
|
||||
Object.assign(req, config);
|
||||
|
||||
return $http(req)
|
||||
.then(({ data }) => {
|
||||
this.set(method, `related.${related}`, data);
|
||||
|
||||
47
awx/ui/client/lib/models/Instance.js
Normal file
47
awx/ui/client/lib/models/Instance.js
Normal file
@@ -0,0 +1,47 @@
|
||||
let Base;
|
||||
|
||||
function createFormSchema (method, config) {
|
||||
if (!config) {
|
||||
config = method;
|
||||
method = 'GET';
|
||||
}
|
||||
|
||||
const schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`));
|
||||
|
||||
if (config && config.omit) {
|
||||
config.omit.forEach(key => delete schema[key]);
|
||||
}
|
||||
|
||||
Object.keys(schema).forEach(key => {
|
||||
schema[key].id = key;
|
||||
|
||||
if (this.has(key)) {
|
||||
schema[key]._value = this.get(key);
|
||||
}
|
||||
});
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
function InstanceModel (method, resource, config) {
|
||||
// Base takes two args: resource and settings
|
||||
// resource is the string endpoint
|
||||
Base.call(this, 'instances');
|
||||
|
||||
this.Constructor = InstanceModel;
|
||||
this.createFormSchema = createFormSchema.bind(this);
|
||||
|
||||
return this.create(method, resource, config);
|
||||
}
|
||||
|
||||
function InstanceModelLoader (BaseModel) {
|
||||
Base = BaseModel;
|
||||
|
||||
return InstanceModel;
|
||||
}
|
||||
|
||||
InstanceModelLoader.$inject = [
|
||||
'BaseModel'
|
||||
];
|
||||
|
||||
export default InstanceModelLoader;
|
||||
47
awx/ui/client/lib/models/InstanceGroup.js
Normal file
47
awx/ui/client/lib/models/InstanceGroup.js
Normal file
@@ -0,0 +1,47 @@
|
||||
let Base;
|
||||
|
||||
function createFormSchema (method, config) {
|
||||
if (!config) {
|
||||
config = method;
|
||||
method = 'GET';
|
||||
}
|
||||
|
||||
const schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`));
|
||||
|
||||
if (config && config.omit) {
|
||||
config.omit.forEach(key => delete schema[key]);
|
||||
}
|
||||
|
||||
Object.keys(schema).forEach(key => {
|
||||
schema[key].id = key;
|
||||
|
||||
if (this.has(key)) {
|
||||
schema[key]._value = this.get(key);
|
||||
}
|
||||
});
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
function InstanceGroupModel (method, resource, config) {
|
||||
// Base takes two args: resource and settings
|
||||
// resource is the string endpoint
|
||||
Base.call(this, 'instance_groups');
|
||||
|
||||
this.Constructor = InstanceGroupModel;
|
||||
this.createFormSchema = createFormSchema.bind(this);
|
||||
|
||||
return this.create(method, resource, config);
|
||||
}
|
||||
|
||||
function InstanceGroupModelLoader (BaseModel) {
|
||||
Base = BaseModel;
|
||||
|
||||
return InstanceGroupModel;
|
||||
}
|
||||
|
||||
InstanceGroupModelLoader.$inject = [
|
||||
'BaseModel'
|
||||
];
|
||||
|
||||
export default InstanceGroupModelLoader;
|
||||
@@ -9,6 +9,8 @@ import Organization from '~models/Organization';
|
||||
import Project from '~models/Project';
|
||||
import JobTemplate from '~models/JobTemplate';
|
||||
import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode';
|
||||
import Instance from '~models/Instance';
|
||||
import InstanceGroup from '~models/InstanceGroup';
|
||||
import InventorySource from '~models/InventorySource';
|
||||
import Inventory from '~models/Inventory';
|
||||
import InventoryScript from '~models/InventoryScript';
|
||||
@@ -34,6 +36,8 @@ angular
|
||||
.service('ProjectModel', Project)
|
||||
.service('JobTemplateModel', JobTemplate)
|
||||
.service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode)
|
||||
.service('InstanceModel', Instance)
|
||||
.service('InstanceGroupModel', InstanceGroup)
|
||||
.service('InventorySourceModel', InventorySource)
|
||||
.service('InventoryModel', Inventory)
|
||||
.service('InventoryScriptModel', InventoryScript)
|
||||
|
||||
@@ -67,6 +67,7 @@ function BaseStringService (namespace) {
|
||||
this.OFF = t.s('OFF');
|
||||
this.YAML = t.s('YAML');
|
||||
this.JSON = t.s('JSON');
|
||||
|
||||
this.deleteResource = {
|
||||
HEADER: t.s('Delete'),
|
||||
USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }),
|
||||
|
||||
@@ -15,7 +15,17 @@
|
||||
background: @at-color-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.at-Button--add {
|
||||
&:extend(.at-Button--success all);
|
||||
&:before {
|
||||
content: "+";
|
||||
font-size: 20px;
|
||||
}
|
||||
border-color: transparent;
|
||||
margin-left: @at-space-2x;
|
||||
}
|
||||
|
||||
.at-Button--info {
|
||||
.at-mixin-Button();
|
||||
.at-mixin-ButtonColor('at-color-info', 'at-color-default');
|
||||
@@ -26,7 +36,7 @@
|
||||
.at-mixin-ButtonColor('at-color-error', 'at-color-default');
|
||||
}
|
||||
|
||||
.at-ButtonHollow--default {
|
||||
.at-ButtonHollow--default {
|
||||
.at-mixin-Button();
|
||||
.at-mixin-ButtonHollow(
|
||||
'at-color-default',
|
||||
@@ -41,5 +51,5 @@
|
||||
}
|
||||
|
||||
.at-Button--expand {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
}
|
||||
|
||||
.at-mixin-Button () {
|
||||
border-radius: @at-border-radius;
|
||||
height: @at-height-input;
|
||||
padding: @at-padding-button-vertical @at-padding-button-horizontal;
|
||||
font-size: @at-font-size-body;
|
||||
@@ -101,4 +102,4 @@
|
||||
|
||||
.at-mixin-FontFixedWidth () {
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,8 @@
|
||||
@at-color-input-icon: @at-gray-b7;
|
||||
@at-color-input-placeholder: @at-gray-848992;
|
||||
@at-color-input-text: @at-gray-161b1f;
|
||||
@at-color-input-slider-thumb: @at-blue;
|
||||
@at-color-input-slider-track: @at-gray-b7;
|
||||
|
||||
@at-color-icon-dismiss: @at-gray-d7;
|
||||
@at-color-icon-popover: @at-gray-848992;
|
||||
|
||||
@@ -72,7 +72,9 @@
|
||||
@import '../../src/home/dashboard/lists/dashboard-list.block.less';
|
||||
@import '../../src/home/dashboard/dashboard.block.less';
|
||||
@import '../../src/instance-groups/capacity-bar/capacity-bar.block.less';
|
||||
@import '../../src/instance-groups/capacity-adjuster/capacity-adjuster.block.less';
|
||||
@import '../../src/instance-groups/instance-group.block.less';
|
||||
@import '../../src/instance-groups/instances/instance-modal.block.less';
|
||||
@import '../../src/inventories-hosts/inventories/insights/insights.block.less';
|
||||
@import '../../src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.block.less';
|
||||
@import '../../src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.block.less';
|
||||
|
||||
@@ -20,10 +20,10 @@ export default function BuildAnchor($log, $filter) {
|
||||
if (activity.operation === 'create' || activity.operation === 'delete'){
|
||||
// the API formats the changes.inventory field as str 'myInventoryName-PrimaryKey'
|
||||
var inventory_id = _.last(activity.changes.inventory.split('-'));
|
||||
url += 'inventories/' + inventory_id + '/groups/edit/' + activity.changes.id;
|
||||
url += 'inventories/inventory/' + inventory_id + '/groups/edit/' + activity.changes.id;
|
||||
}
|
||||
else {
|
||||
url += 'inventories/' + activity.summary_fields.inventory[0].id + '/groups/edit/' + (activity.changes.id || activity.changes.object1_pk);
|
||||
url += 'inventories/inventory/' + activity.summary_fields.inventory[0].id + '/groups/edit/' + (activity.changes.id || activity.changes.object1_pk);
|
||||
}
|
||||
break;
|
||||
case 'host':
|
||||
@@ -33,7 +33,7 @@ export default function BuildAnchor($log, $filter) {
|
||||
url += 'jobs/' + obj.id;
|
||||
break;
|
||||
case 'inventory':
|
||||
url += 'inventories/' + obj.id + '/';
|
||||
url += obj.kind && obj.kind === "smart" ? 'inventories/smart/' + obj.id + '/' : 'inventories/inventory' + obj.id + '/';
|
||||
break;
|
||||
case 'schedule':
|
||||
// schedule urls depend on the resource they're associated with
|
||||
|
||||
@@ -97,7 +97,6 @@ angular
|
||||
users.name,
|
||||
projects.name,
|
||||
scheduler.name,
|
||||
instanceGroups.name,
|
||||
|
||||
'Utilities',
|
||||
'templates',
|
||||
@@ -105,6 +104,7 @@ angular
|
||||
'AWDirectives',
|
||||
'features',
|
||||
|
||||
instanceGroups,
|
||||
atFeatures,
|
||||
atLibComponents,
|
||||
atLibModels,
|
||||
|
||||
@@ -17,6 +17,11 @@ export default [
|
||||
'configurationGithubTeamForm',
|
||||
'configurationGoogleForm',
|
||||
'configurationLdapForm',
|
||||
'configurationLdap1Form',
|
||||
'configurationLdap2Form',
|
||||
'configurationLdap3Form',
|
||||
'configurationLdap4Form',
|
||||
'configurationLdap5Form',
|
||||
'configurationRadiusForm',
|
||||
'configurationTacacsForm',
|
||||
'configurationSamlForm',
|
||||
@@ -39,6 +44,11 @@ export default [
|
||||
configurationGithubTeamForm,
|
||||
configurationGoogleForm,
|
||||
configurationLdapForm,
|
||||
configurationLdap1Form,
|
||||
configurationLdap2Form,
|
||||
configurationLdap3Form,
|
||||
configurationLdap4Form,
|
||||
configurationLdap5Form,
|
||||
configurationRadiusForm,
|
||||
configurationTacacsForm,
|
||||
configurationSamlForm,
|
||||
@@ -55,6 +65,8 @@ export default [
|
||||
var formTracker = $scope.$parent.vm.formTracker;
|
||||
var dropdownValue = 'azure';
|
||||
var activeAuthForm = 'azure';
|
||||
var ldapDropdownValue = '';
|
||||
|
||||
let codeInputInitialized = false;
|
||||
|
||||
// Default active form
|
||||
@@ -62,10 +74,16 @@ export default [
|
||||
formTracker.setCurrentAuth(activeAuthForm);
|
||||
}
|
||||
|
||||
var activeForm = function() {
|
||||
const getActiveAuthForm = () => {
|
||||
if (authVm.dropdownValue === 'ldap') {
|
||||
return `ldap${authVm.ldapDropdownValue}`;
|
||||
}
|
||||
return authVm.dropdownValue;
|
||||
};
|
||||
|
||||
var activeForm = function() {
|
||||
if(!$scope.$parent[formTracker.currentFormName()].$dirty) {
|
||||
authVm.activeAuthForm = authVm.dropdownValue;
|
||||
authVm.activeAuthForm = getActiveAuthForm();
|
||||
formTracker.setCurrentAuth(authVm.activeAuthForm);
|
||||
startCodeMirrors();
|
||||
} else {
|
||||
@@ -78,7 +96,7 @@ export default [
|
||||
onClick: function() {
|
||||
$scope.$parent.vm.populateFromApi();
|
||||
$scope.$parent[formTracker.currentFormName()].$setPristine();
|
||||
authVm.activeAuthForm = authVm.dropdownValue;
|
||||
authVm.activeAuthForm = getActiveAuthForm();
|
||||
formTracker.setCurrentAuth(authVm.activeAuthForm);
|
||||
$('#FormModal-dialog').dialog('close');
|
||||
}
|
||||
@@ -89,7 +107,7 @@ export default [
|
||||
.then(function() {
|
||||
$scope.$parent[formTracker.currentFormName()].$setPristine();
|
||||
$scope.$parent.vm.populateFromApi();
|
||||
authVm.activeAuthForm = authVm.dropdownValue;
|
||||
authVm.activeAuthForm = getActiveAuthForm();
|
||||
formTracker.setCurrentAuth(authVm.activeAuthForm);
|
||||
$('#FormModal-dialog').dialog('close');
|
||||
});
|
||||
@@ -100,11 +118,12 @@ export default [
|
||||
$scope.$parent.vm.triggerModal(msg, title, buttons);
|
||||
}
|
||||
formTracker.setCurrentAuth(authVm.activeAuthForm);
|
||||
authVm.ldapSelected = (authVm.activeAuthForm.indexOf('ldap') !== -1);
|
||||
};
|
||||
|
||||
var dropdownOptions = [
|
||||
{label: i18n._('Azure AD'), value: 'azure'},
|
||||
{label: i18n._('GitHub'), value: 'github'},
|
||||
{label: i18n._('GitHub'), value: 'github'},
|
||||
{label: i18n._('GitHub Org'), value: 'github_org'},
|
||||
{label: i18n._('GitHub Team'), value: 'github_team'},
|
||||
{label: i18n._('Google OAuth2'), value: 'google_oauth'},
|
||||
@@ -114,48 +133,97 @@ export default [
|
||||
{label: i18n._('TACACS+'), value: 'tacacs'}
|
||||
];
|
||||
|
||||
var ldapDropdownOptions = [
|
||||
{label: i18n._('Default'), value: ''},
|
||||
{label: i18n._('LDAP 1 (Optional)'), value: '1'},
|
||||
{label: i18n._('LDAP 2 (Optional)'), value: '2'},
|
||||
{label: i18n._('LDAP 3 (Optional)'), value: '3'},
|
||||
{label: i18n._('LDAP 4 (Optional)'), value: '4'},
|
||||
{label: i18n._('LDAP 5 (Optional)'), value: '5'},
|
||||
];
|
||||
|
||||
CreateSelect2({
|
||||
element: '#configure-dropdown-nav',
|
||||
multiple: false,
|
||||
});
|
||||
|
||||
var authForms = [{
|
||||
CreateSelect2({
|
||||
element: '#configure-ldap-dropdown',
|
||||
multiple: false,
|
||||
});
|
||||
|
||||
var authForms = [
|
||||
{
|
||||
formDef: configurationAzureForm,
|
||||
id: 'auth-azure-form',
|
||||
name: 'azure'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
formDef: configurationGithubForm,
|
||||
id: 'auth-github-form',
|
||||
name: 'github'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
formDef: configurationGithubOrgForm,
|
||||
id: 'auth-github-org-form',
|
||||
name: 'github_org'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
formDef: configurationGithubTeamForm,
|
||||
id: 'auth-github-team-form',
|
||||
name: 'github_team'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
formDef: configurationGoogleForm,
|
||||
id: 'auth-google-form',
|
||||
name: 'google_oauth'
|
||||
}, {
|
||||
formDef: configurationLdapForm,
|
||||
id: 'auth-ldap-form',
|
||||
name: 'ldap'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
formDef: configurationRadiusForm,
|
||||
id: 'auth-radius-form',
|
||||
name: 'radius'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
formDef: configurationTacacsForm,
|
||||
id: 'auth-tacacs-form',
|
||||
name: 'tacacs'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
formDef: configurationSamlForm,
|
||||
id: 'auth-saml-form',
|
||||
name: 'saml'
|
||||
}, ];
|
||||
},
|
||||
{
|
||||
formDef: configurationLdapForm,
|
||||
id: 'auth-ldap-form',
|
||||
name: 'ldap'
|
||||
},
|
||||
{
|
||||
formDef: configurationLdap1Form,
|
||||
id: 'auth-ldap1-form',
|
||||
name: 'ldap1'
|
||||
},
|
||||
{
|
||||
formDef: configurationLdap2Form,
|
||||
id: 'auth-ldap2-form',
|
||||
name: 'ldap2'
|
||||
},
|
||||
{
|
||||
formDef: configurationLdap3Form,
|
||||
id: 'auth-ldap3-form',
|
||||
name: 'ldap3'
|
||||
},
|
||||
{
|
||||
formDef: configurationLdap4Form,
|
||||
id: 'auth-ldap4-form',
|
||||
name: 'ldap4'
|
||||
},
|
||||
{
|
||||
formDef: configurationLdap5Form,
|
||||
id: 'auth-ldap5-form',
|
||||
name: 'ldap5'
|
||||
},
|
||||
];
|
||||
|
||||
var forms = _.pluck(authForms, 'formDef');
|
||||
_.each(forms, function(form) {
|
||||
@@ -179,10 +247,8 @@ export default [
|
||||
form.buttons.save.disabled = $rootScope.user_is_system_auditor;
|
||||
});
|
||||
|
||||
function startCodeMirrors(key){
|
||||
var form = _.find(authForms, function(f){
|
||||
return f.name === $scope.authVm.activeAuthForm;
|
||||
});
|
||||
function startCodeMirrors(key) {
|
||||
var form = _.find(authForms, f => f.name === $scope.authVm.activeAuthForm);
|
||||
|
||||
if(!key){
|
||||
// Attach codemirror to fields that need it
|
||||
@@ -246,9 +312,23 @@ export default [
|
||||
// Flag to avoid re-rendering and breaking Select2 dropdowns on tab switching
|
||||
var dropdownRendered = false;
|
||||
|
||||
function populateLDAPGroupType(flag){
|
||||
if($scope.$parent.AUTH_LDAP_GROUP_TYPE !== null) {
|
||||
$scope.$parent.AUTH_LDAP_GROUP_TYPE = _.find($scope.$parent.AUTH_LDAP_GROUP_TYPE_options, { value: $scope.$parent.AUTH_LDAP_GROUP_TYPE });
|
||||
function populateLDAPGroupType(flag, index = null){
|
||||
let groupPropName;
|
||||
let groupOptionsPropName;
|
||||
let selectElementId;
|
||||
|
||||
if (index) {
|
||||
groupPropName = `AUTH_LDAP_${index}_GROUP_TYPE`;
|
||||
groupOptionsPropName = `${groupPropName}_options`;
|
||||
selectElementId = `#configuration_ldap${index}_template_${groupPropName}`;
|
||||
} else {
|
||||
groupPropName = 'AUTH_LDAP_GROUP_TYPE';
|
||||
groupOptionsPropName = `${groupPropName}_options`;
|
||||
selectElementId = `#configuration_ldap_template_${groupPropName}`;
|
||||
}
|
||||
|
||||
if($scope.$parent[groupPropName] !== null) {
|
||||
$scope.$parent[groupPropName] = _.find($scope[groupOptionsPropName], { value: $scope.$parent[groupPropName] });
|
||||
}
|
||||
|
||||
if(flag !== undefined){
|
||||
@@ -258,7 +338,7 @@ export default [
|
||||
if(!dropdownRendered) {
|
||||
dropdownRendered = true;
|
||||
CreateSelect2({
|
||||
element: '#configuration_ldap_template_AUTH_LDAP_GROUP_TYPE',
|
||||
element: selectElementId,
|
||||
multiple: false,
|
||||
placeholder: i18n._('Select group types'),
|
||||
});
|
||||
@@ -284,13 +364,12 @@ export default [
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('AUTH_LDAP_GROUP_TYPE_populated', function(e, data, flag) {
|
||||
populateLDAPGroupType(flag);
|
||||
});
|
||||
|
||||
$scope.$on('TACACSPLUS_AUTH_PROTOCOL_populated', function(e, data, flag) {
|
||||
populateTacacsProtocol(flag);
|
||||
});
|
||||
$scope.$on('AUTH_LDAP_GROUP_TYPE_populated', (e, data, flag) => populateLDAPGroupType(flag));
|
||||
$scope.$on('AUTH_LDAP_1_GROUP_TYPE_populated', (e, data, flag) => populateLDAPGroupType(flag, 1));
|
||||
$scope.$on('AUTH_LDAP_2_GROUP_TYPE_populated', (e, data, flag) => populateLDAPGroupType(flag, 2));
|
||||
$scope.$on('AUTH_LDAP_3_GROUP_TYPE_populated', (e, data, flag) => populateLDAPGroupType(flag, 3));
|
||||
$scope.$on('AUTH_LDAP_4_GROUP_TYPE_populated', (e, data, flag) => populateLDAPGroupType(flag, 4));
|
||||
$scope.$on('AUTH_LDAP_5_GROUP_TYPE_populated', (e, data, flag) => populateLDAPGroupType(flag, 5));
|
||||
|
||||
$scope.$on('$locationChangeStart', (event, url) => {
|
||||
let parts = url.split('/');
|
||||
@@ -311,6 +390,12 @@ export default [
|
||||
}
|
||||
|
||||
populateLDAPGroupType(false);
|
||||
populateLDAPGroupType(false, 1);
|
||||
populateLDAPGroupType(false, 2);
|
||||
populateLDAPGroupType(false, 3);
|
||||
populateLDAPGroupType(false, 4);
|
||||
populateLDAPGroupType(false, 5);
|
||||
|
||||
populateTacacsProtocol(false);
|
||||
});
|
||||
|
||||
@@ -328,7 +413,9 @@ export default [
|
||||
activeAuthForm: activeAuthForm,
|
||||
authForms: authForms,
|
||||
dropdownOptions: dropdownOptions,
|
||||
dropdownValue: dropdownValue
|
||||
dropdownValue: dropdownValue,
|
||||
ldapDropdownValue: ldapDropdownValue,
|
||||
ldapDropdownOptions: ldapDropdownOptions,
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,63 +1,89 @@
|
||||
<div class="tab-pane Configuration-container" id="configuration_edit">
|
||||
|
||||
<div class="Form-nav--dropdownContainer">
|
||||
<div class="Form-nav--dropdownLabel" translate>Sub Category</div>
|
||||
<div class="Form-nav--dropdown">
|
||||
<select
|
||||
id="configure-dropdown-nav"
|
||||
class="form-control"
|
||||
ng-model="authVm.dropdownValue"
|
||||
ng-options="opt.value as opt.label for opt in authVm.dropdownOptions"
|
||||
ng-change="authVm.activeForm()">
|
||||
</select>
|
||||
<select
|
||||
id="configure-dropdown-nav"
|
||||
class="form-control"
|
||||
ng-model="authVm.dropdownValue"
|
||||
ng-options="opt.value as opt.label for opt in authVm.dropdownOptions"
|
||||
ng-change="authVm.activeForm()">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Form-nav--ldapDropdownContainer" ng-show="authVm.dropdownValue === 'ldap'">
|
||||
<div class="Form-nav--dropdownLabel" translate>LDAP Server</div>
|
||||
<div class="Form-nav--dropdown">
|
||||
<select
|
||||
id="configure-ldap-dropdown"
|
||||
class="form-control"
|
||||
ng-model="authVm.ldapDropdownValue"
|
||||
ng-options="opt.value as opt.label for opt in authVm.ldapDropdownOptions"
|
||||
ng-change="authVm.activeForm()">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div ng-show="authVm.activeAuthForm === 'azure'">
|
||||
<div id="auth-azure-form">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'github'">
|
||||
<div id="auth-github-form">
|
||||
<hr ng-show="authVm.dropdownValue === 'ldap'"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'github_org'">
|
||||
<div id="auth-github-org-form">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div ng-show="authVm.activeAuthForm === 'azure'">
|
||||
<div id="auth-azure-form"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'github_team'">
|
||||
<div id="auth-github-team-form">
|
||||
<div ng-show="authVm.activeAuthForm === 'github'">
|
||||
<div id="auth-github-form"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'google_oauth'">
|
||||
<div id="auth-google-form">
|
||||
<div ng-show="authVm.activeAuthForm === 'github_org'">
|
||||
<div id="auth-github-org-form"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'ldap'">
|
||||
<div id="auth-ldap-form">
|
||||
<div ng-show="authVm.activeAuthForm === 'github_team'">
|
||||
<div id="auth-github-team-form"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'radius'">
|
||||
<div id="auth-radius-form">
|
||||
<div ng-show="authVm.activeAuthForm === 'google_oauth'">
|
||||
<div id="auth-google-form"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'tacacs'">
|
||||
<div id="auth-tacacs-form">
|
||||
<div ng-show="authVm.activeAuthForm === 'ldap'">
|
||||
<div id="auth-ldap-form"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'saml'">
|
||||
<div id="auth-saml-form">
|
||||
<div ng-show="authVm.activeAuthForm === 'ldap1'">
|
||||
<div id="auth-ldap1-form"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="authVm.activeAuthForm === 'ldap2'">
|
||||
<div id="auth-ldap2-form"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="authVm.activeAuthForm === 'ldap3'">
|
||||
<div id="auth-ldap3-form"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="authVm.activeAuthForm === 'ldap4'">
|
||||
<div id="auth-ldap4-form"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="authVm.activeAuthForm === 'ldap5'">
|
||||
<div id="auth-ldap5-form"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="authVm.activeAuthForm === 'radius'">
|
||||
<div id="auth-radius-form"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="authVm.activeAuthForm === 'tacacs'">
|
||||
<div id="auth-tacacs-form"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="authVm.activeAuthForm === 'saml'">
|
||||
<div id="auth-saml-form"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
// editTitle: 'Authorization Configuration',
|
||||
name: 'configuration_ldap1_template',
|
||||
showActions: true,
|
||||
showHeader: false,
|
||||
|
||||
fields: {
|
||||
AUTH_LDAP_1_SERVER_URI: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_1_SERVER_URI'
|
||||
},
|
||||
AUTH_LDAP_1_BIND_DN: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_1_BIND_DN'
|
||||
},
|
||||
AUTH_LDAP_1_BIND_PASSWORD: {
|
||||
type: 'sensitive',
|
||||
hasShowInputButton: true,
|
||||
},
|
||||
AUTH_LDAP_1_USER_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_1_USER_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_1_GROUP_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_1_GROUP_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_1_USER_DN_TEMPLATE: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_1_USER_DN_TEMPLATE'
|
||||
},
|
||||
AUTH_LDAP_1_USER_ATTR_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_1_USER_ATTR_MAP',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_1_GROUP_TYPE: {
|
||||
type: 'select',
|
||||
reset: 'AUTH_LDAP_1_GROUP_TYPE',
|
||||
ngOptions: 'group.label for group in AUTH_LDAP_1_GROUP_TYPE_options track by group.value',
|
||||
},
|
||||
AUTH_LDAP_1_REQUIRE_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_1_REQUIRE_GROUP'
|
||||
},
|
||||
AUTH_LDAP_1_DENY_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_1_DENY_GROUP'
|
||||
},
|
||||
AUTH_LDAP_1_START_TLS: {
|
||||
type: 'toggleSwitch'
|
||||
},
|
||||
AUTH_LDAP_1_USER_FLAGS_BY_GROUP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_1_USER_FLAGS_BY_GROUP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_1_ORGANIZATION_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_1_ORGANIZATION_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_1_TEAM_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_1_TEAM_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
reset: {
|
||||
ngShow: '!user_is_system_auditor',
|
||||
ngClick: 'vm.resetAllConfirm()',
|
||||
label: i18n._('Revert all to default'),
|
||||
class: 'Form-resetAll'
|
||||
},
|
||||
cancel: {
|
||||
ngClick: 'vm.formCancel()',
|
||||
},
|
||||
save: {
|
||||
ngClick: 'vm.formSave()',
|
||||
ngDisabled: "!ldap_auth || configuration_ldap1_template_form.$invalid || configuration_ldap1_template_form.$pending"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,108 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
// editTitle: 'Authorization Configuration',
|
||||
name: 'configuration_ldap2_template',
|
||||
showActions: true,
|
||||
showHeader: false,
|
||||
|
||||
fields: {
|
||||
AUTH_LDAP_2_SERVER_URI: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_2_SERVER_URI'
|
||||
},
|
||||
AUTH_LDAP_2_BIND_DN: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_2_BIND_DN'
|
||||
},
|
||||
AUTH_LDAP_2_BIND_PASSWORD: {
|
||||
type: 'sensitive',
|
||||
hasShowInputButton: true,
|
||||
},
|
||||
AUTH_LDAP_2_USER_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_2_USER_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_2_GROUP_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_2_GROUP_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_2_USER_DN_TEMPLATE: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_2_USER_DN_TEMPLATE'
|
||||
},
|
||||
AUTH_LDAP_2_USER_ATTR_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_2_USER_ATTR_MAP',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_2_GROUP_TYPE: {
|
||||
type: 'select',
|
||||
reset: 'AUTH_LDAP_2_GROUP_TYPE',
|
||||
ngOptions: 'group.label for group in AUTH_LDAP_2_GROUP_TYPE_options track by group.value',
|
||||
},
|
||||
AUTH_LDAP_2_REQUIRE_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_2_REQUIRE_GROUP'
|
||||
},
|
||||
AUTH_LDAP_2_DENY_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_2_DENY_GROUP'
|
||||
},
|
||||
AUTH_LDAP_2_START_TLS: {
|
||||
type: 'toggleSwitch'
|
||||
},
|
||||
AUTH_LDAP_2_USER_FLAGS_BY_GROUP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_2_USER_FLAGS_BY_GROUP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_2_ORGANIZATION_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_2_ORGANIZATION_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_2_TEAM_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_2_TEAM_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
reset: {
|
||||
ngShow: '!user_is_system_auditor',
|
||||
ngClick: 'vm.resetAllConfirm()',
|
||||
label: i18n._('Revert all to default'),
|
||||
class: 'Form-resetAll'
|
||||
},
|
||||
cancel: {
|
||||
ngClick: 'vm.formCancel()',
|
||||
},
|
||||
save: {
|
||||
ngClick: 'vm.formSave()',
|
||||
ngDisabled: "!ldap_auth || configuration_ldap2_template_form.$invalid || configuration_ldap2_template_form.$pending"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,108 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
// editTitle: 'Authorization Configuration',
|
||||
name: 'configuration_ldap3_template',
|
||||
showActions: true,
|
||||
showHeader: false,
|
||||
|
||||
fields: {
|
||||
AUTH_LDAP_3_SERVER_URI: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_3_SERVER_URI'
|
||||
},
|
||||
AUTH_LDAP_3_BIND_DN: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_3_BIND_DN'
|
||||
},
|
||||
AUTH_LDAP_3_BIND_PASSWORD: {
|
||||
type: 'sensitive',
|
||||
hasShowInputButton: true,
|
||||
},
|
||||
AUTH_LDAP_3_USER_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_3_USER_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_3_GROUP_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_3_GROUP_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_3_USER_DN_TEMPLATE: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_3_USER_DN_TEMPLATE'
|
||||
},
|
||||
AUTH_LDAP_3_USER_ATTR_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_3_USER_ATTR_MAP',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_3_GROUP_TYPE: {
|
||||
type: 'select',
|
||||
reset: 'AUTH_LDAP_3_GROUP_TYPE',
|
||||
ngOptions: 'group.label for group in AUTH_LDAP_3_GROUP_TYPE_options track by group.value',
|
||||
},
|
||||
AUTH_LDAP_3_REQUIRE_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_3_REQUIRE_GROUP'
|
||||
},
|
||||
AUTH_LDAP_3_DENY_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_3_DENY_GROUP'
|
||||
},
|
||||
AUTH_LDAP_3_START_TLS: {
|
||||
type: 'toggleSwitch'
|
||||
},
|
||||
AUTH_LDAP_3_USER_FLAGS_BY_GROUP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_3_USER_FLAGS_BY_GROUP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_3_ORGANIZATION_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_3_ORGANIZATION_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_3_TEAM_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_3_TEAM_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
reset: {
|
||||
ngShow: '!user_is_system_auditor',
|
||||
ngClick: 'vm.resetAllConfirm()',
|
||||
label: i18n._('Revert all to default'),
|
||||
class: 'Form-resetAll'
|
||||
},
|
||||
cancel: {
|
||||
ngClick: 'vm.formCancel()',
|
||||
},
|
||||
save: {
|
||||
ngClick: 'vm.formSave()',
|
||||
ngDisabled: "!ldap_auth || configuration_ldap3_template_form.$invalid || configuration_ldap3_template_form.$pending"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,108 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
// editTitle: 'Authorization Configuration',
|
||||
name: 'configuration_ldap4_template',
|
||||
showActions: true,
|
||||
showHeader: false,
|
||||
|
||||
fields: {
|
||||
AUTH_LDAP_4_SERVER_URI: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_4_SERVER_URI'
|
||||
},
|
||||
AUTH_LDAP_4_BIND_DN: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_4_BIND_DN'
|
||||
},
|
||||
AUTH_LDAP_4_BIND_PASSWORD: {
|
||||
type: 'sensitive',
|
||||
hasShowInputButton: true,
|
||||
},
|
||||
AUTH_LDAP_4_USER_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_4_USER_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_4_GROUP_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_4_GROUP_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_4_USER_DN_TEMPLATE: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_4_USER_DN_TEMPLATE'
|
||||
},
|
||||
AUTH_LDAP_4_USER_ATTR_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_4_USER_ATTR_MAP',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_4_GROUP_TYPE: {
|
||||
type: 'select',
|
||||
reset: 'AUTH_LDAP_4_GROUP_TYPE',
|
||||
ngOptions: 'group.label for group in AUTH_LDAP_4_GROUP_TYPE_options track by group.value',
|
||||
},
|
||||
AUTH_LDAP_4_REQUIRE_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_4_REQUIRE_GROUP'
|
||||
},
|
||||
AUTH_LDAP_4_DENY_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_4_DENY_GROUP'
|
||||
},
|
||||
AUTH_LDAP_4_START_TLS: {
|
||||
type: 'toggleSwitch'
|
||||
},
|
||||
AUTH_LDAP_4_USER_FLAGS_BY_GROUP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_4_USER_FLAGS_BY_GROUP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_4_ORGANIZATION_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_4_ORGANIZATION_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_4_TEAM_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_4_TEAM_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
reset: {
|
||||
ngShow: '!user_is_system_auditor',
|
||||
ngClick: 'vm.resetAllConfirm()',
|
||||
label: i18n._('Revert all to default'),
|
||||
class: 'Form-resetAll'
|
||||
},
|
||||
cancel: {
|
||||
ngClick: 'vm.formCancel()',
|
||||
},
|
||||
save: {
|
||||
ngClick: 'vm.formSave()',
|
||||
ngDisabled: "!ldap_auth || configuration_ldap4_template_form.$invalid || configuration_ldap4_template_form.$pending"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,108 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
// editTitle: 'Authorization Configuration',
|
||||
name: 'configuration_ldap5_template',
|
||||
showActions: true,
|
||||
showHeader: false,
|
||||
|
||||
fields: {
|
||||
AUTH_LDAP_5_SERVER_URI: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_5_SERVER_URI'
|
||||
},
|
||||
AUTH_LDAP_5_BIND_DN: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_5_BIND_DN'
|
||||
},
|
||||
AUTH_LDAP_5_BIND_PASSWORD: {
|
||||
type: 'sensitive',
|
||||
hasShowInputButton: true,
|
||||
},
|
||||
AUTH_LDAP_5_USER_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_5_USER_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_5_GROUP_SEARCH: {
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
reset: 'AUTH_LDAP_5_GROUP_SEARCH'
|
||||
},
|
||||
AUTH_LDAP_5_USER_DN_TEMPLATE: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_5_USER_DN_TEMPLATE'
|
||||
},
|
||||
AUTH_LDAP_5_USER_ATTR_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_5_USER_ATTR_MAP',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_5_GROUP_TYPE: {
|
||||
type: 'select',
|
||||
reset: 'AUTH_LDAP_5_GROUP_TYPE',
|
||||
ngOptions: 'group.label for group in AUTH_LDAP_5_GROUP_TYPE_options track by group.value',
|
||||
},
|
||||
AUTH_LDAP_5_REQUIRE_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_5_REQUIRE_GROUP'
|
||||
},
|
||||
AUTH_LDAP_5_DENY_GROUP: {
|
||||
type: 'text',
|
||||
reset: 'AUTH_LDAP_5_DENY_GROUP'
|
||||
},
|
||||
AUTH_LDAP_5_START_TLS: {
|
||||
type: 'toggleSwitch'
|
||||
},
|
||||
AUTH_LDAP_5_USER_FLAGS_BY_GROUP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_5_USER_FLAGS_BY_GROUP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_5_ORGANIZATION_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_5_ORGANIZATION_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
AUTH_LDAP_5_TEAM_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'AUTH_LDAP_5_TEAM_MAP',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
reset: {
|
||||
ngShow: '!user_is_system_auditor',
|
||||
ngClick: 'vm.resetAllConfirm()',
|
||||
label: i18n._('Revert all to default'),
|
||||
class: 'Form-resetAll'
|
||||
},
|
||||
cancel: {
|
||||
ngClick: 'vm.formCancel()',
|
||||
},
|
||||
save: {
|
||||
ngClick: 'vm.formSave()',
|
||||
ngDisabled: "!ldap_auth || configuration_ldap5_template_form.$invalid || configuration_ldap5_template_form.$pending"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -71,15 +71,49 @@ export default ['i18n', function(i18n) {
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth'
|
||||
},
|
||||
SOCIAL_AUTH_SAML_ORGANIZATION_ATTR: {
|
||||
type: 'textarea',
|
||||
reset: 'SOCIAL_AUTH_SAML_ORGANIZATION_ATTR',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth'
|
||||
},
|
||||
SOCIAL_AUTH_SAML_TEAM_MAP: {
|
||||
type: 'textarea',
|
||||
reset: 'SOCIAL_AUTH_SAML_TEAM_MAP',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth'
|
||||
}
|
||||
},
|
||||
SOCIAL_AUTH_SAML_TEAM_ATTR: {
|
||||
type: 'textarea',
|
||||
reset: 'SOCIAL_AUTH_SAML_TEAM_ATTR',
|
||||
rows: 6,
|
||||
codeMirror: true,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth'
|
||||
},
|
||||
SOCIAL_AUTH_SAML_SECURITY_CONFIG: {
|
||||
type: 'textarea',
|
||||
reset: 'SOCIAL_AUTH_SAML_SECURITY_CONFIG',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
SOCIAL_AUTH_SAML_SP_EXTRA: {
|
||||
type: 'textarea',
|
||||
reset: 'SOCIAL_AUTH_SAML_SP_EXTRA',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
SOCIAL_AUTH_SAML_EXTRA_DATA: {
|
||||
type: 'textarea',
|
||||
reset: 'SOCIAL_AUTH_SAML_EXTRA_DATA',
|
||||
codeMirror: true,
|
||||
rows: 6,
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
},
|
||||
},
|
||||
|
||||
buttons: {
|
||||
reset: {
|
||||
ngShow: '!user_is_system_auditor',
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.Form-nav--ldapDropdownContainer {
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 0 0 auto auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Form-nav--dropdown {
|
||||
width: 285px;
|
||||
}
|
||||
@@ -166,3 +173,7 @@ input#filePickerText {
|
||||
.LogAggregator-failedNotification{
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ export default [
|
||||
'configurationGithubTeamForm',
|
||||
'configurationGoogleForm',
|
||||
'configurationLdapForm',
|
||||
'configurationLdap1Form',
|
||||
'configurationLdap2Form',
|
||||
'configurationLdap3Form',
|
||||
'configurationLdap4Form',
|
||||
'configurationLdap5Form',
|
||||
'configurationRadiusForm',
|
||||
'configurationTacacsForm',
|
||||
'configurationSamlForm',
|
||||
@@ -34,6 +39,11 @@ export default [
|
||||
configurationGithubTeamForm,
|
||||
configurationGoogleForm,
|
||||
configurationLdapForm,
|
||||
configurationLdap1Form,
|
||||
configurationLdap2Form,
|
||||
configurationLdap3Form,
|
||||
configurationLdap4Form,
|
||||
configurationLdap5Form,
|
||||
configurationRadiusForm,
|
||||
configurationTacacsForm,
|
||||
configurationSamlForm,
|
||||
@@ -52,6 +62,11 @@ export default [
|
||||
'github_team': configurationGithubTeamForm,
|
||||
'google_oauth': configurationGoogleForm,
|
||||
'ldap': configurationLdapForm,
|
||||
'ldap1': configurationLdap1Form,
|
||||
'ldap2': configurationLdap2Form,
|
||||
'ldap3': configurationLdap3Form,
|
||||
'ldap4': configurationLdap4Form,
|
||||
'ldap5': configurationLdap5Form,
|
||||
'radius': configurationRadiusForm,
|
||||
'tacacs': configurationTacacsForm,
|
||||
'saml': configurationSamlForm,
|
||||
@@ -85,9 +100,14 @@ export default [
|
||||
// the ConfigurationUtils.arrayToList()
|
||||
// does a string.split(', ') w/ an extra space
|
||||
// behind the comma.
|
||||
|
||||
const isLdap = (key.indexOf("AUTH_LDAP") !== -1);
|
||||
const isLdapUserSearch = isLdap && (key.indexOf("USER_SEARCH") !== -1);
|
||||
const isLdapGroupSearch = isLdap && (key.indexOf("GROUP_SEARCH") !== -1);
|
||||
|
||||
if(key === "AD_HOC_COMMANDS"){
|
||||
$scope[key] = data[key];
|
||||
} else if (key === "AUTH_LDAP_USER_SEARCH" || key === "AUTH_LDAP_GROUP_SEARCH") {
|
||||
} else if (isLdapUserSearch || isLdapGroupSearch) {
|
||||
$scope[key] = JSON.stringify(data[key]);
|
||||
} else {
|
||||
$scope[key] = ConfigurationUtils.arrayToList(data[key], key);
|
||||
@@ -339,7 +359,12 @@ export default [
|
||||
$scope.$broadcast(key+'_reverted');
|
||||
}
|
||||
else if($scope[key + '_field'].hasOwnProperty('codeMirror')){
|
||||
if (key === "AUTH_LDAP_USER_SEARCH" || key === "AUTH_LDAP_GROUP_SEARCH") {
|
||||
const isLdap = (key.indexOf("AUTH_LDAP") !== -1);
|
||||
|
||||
const isLdapUserSearch = isLdap && (key.indexOf("USER_SEARCH") !== -1);
|
||||
const isLdapGroupSearch = isLdap && (key.indexOf("GROUP_SEARCH") !== -1);
|
||||
|
||||
if (isLdapUserSearch || isLdapGroupSearch) {
|
||||
$scope[key] = '[]';
|
||||
} else {
|
||||
$scope[key] = '{}';
|
||||
|
||||
@@ -17,6 +17,11 @@ import configurationGithubOrgForm from './auth-form/sub-forms/auth-github-org.fo
|
||||
import configurationGithubTeamForm from './auth-form/sub-forms/auth-github-team.form';
|
||||
import configurationGoogleForm from './auth-form/sub-forms/auth-google-oauth2.form';
|
||||
import configurationLdapForm from './auth-form/sub-forms/auth-ldap.form.js';
|
||||
import configurationLdap1Form from './auth-form/sub-forms/auth-ldap1.form.js';
|
||||
import configurationLdap2Form from './auth-form/sub-forms/auth-ldap2.form.js';
|
||||
import configurationLdap3Form from './auth-form/sub-forms/auth-ldap3.form.js';
|
||||
import configurationLdap4Form from './auth-form/sub-forms/auth-ldap4.form.js';
|
||||
import configurationLdap5Form from './auth-form/sub-forms/auth-ldap5.form.js';
|
||||
import configurationRadiusForm from './auth-form/sub-forms/auth-radius.form.js';
|
||||
import configurationTacacsForm from './auth-form/sub-forms/auth-tacacs.form.js';
|
||||
import configurationSamlForm from './auth-form/sub-forms/auth-saml.form';
|
||||
@@ -39,6 +44,11 @@ angular.module('configuration', [])
|
||||
.factory('configurationGithubTeamForm', configurationGithubTeamForm)
|
||||
.factory('configurationGoogleForm', configurationGoogleForm)
|
||||
.factory('configurationLdapForm', configurationLdapForm)
|
||||
.factory('configurationLdap1Form', configurationLdap1Form)
|
||||
.factory('configurationLdap2Form', configurationLdap2Form)
|
||||
.factory('configurationLdap3Form', configurationLdap3Form)
|
||||
.factory('configurationLdap4Form', configurationLdap4Form)
|
||||
.factory('configurationLdap5Form', configurationLdap5Form)
|
||||
.factory('configurationRadiusForm', configurationRadiusForm)
|
||||
.factory('configurationTacacsForm', configurationTacacsForm)
|
||||
.factory('configurationSamlForm', configurationSamlForm)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<at-panel>
|
||||
<at-panel-heading>
|
||||
{{ vm.panelTitle }}
|
||||
</at-panel-heading>
|
||||
|
||||
<at-tab-group>
|
||||
<at-tab state="vm.tab.details">{{:: vm.strings.get('tab.DETAILS') }}</at-tab>
|
||||
<at-tab state="vm.tab.instances">{{:: vm.strings.get('tab.INSTANCES') }}</at-tab>
|
||||
<at-tab state="vm.tab.jobs">{{:: vm.strings.get('tab.JOBS') }}</at-tab>
|
||||
</at-tab-group>
|
||||
|
||||
<at-panel-body>
|
||||
<at-form state="vm.form" autocomplete="off">
|
||||
|
||||
<at-input-text col="4" tab="1" state="vm.form.name"></at-input-text>
|
||||
|
||||
<at-input-text col="4" tab="3" state="vm.form.policy_instance_minimum"></at-input-text>
|
||||
|
||||
<at-input-slider col="4" tab="3" state="vm.form.policy_instance_percentage"></at-input-slider>
|
||||
|
||||
<at-input-lookup col="4" tab="4" state="vm.form.policy_instance_list"></at-input-lookup>
|
||||
<div ui-view="modal"></div>
|
||||
|
||||
<at-action-group col="12" pos="right">
|
||||
<at-form-action type="cancel" to="instanceGroups"></at-form-action>
|
||||
<at-form-action type="save"></at-form-action>
|
||||
</at-action-group>
|
||||
</at-form>
|
||||
</at-panel-body>
|
||||
</at-panel>
|
||||
@@ -0,0 +1,44 @@
|
||||
function AddController ($scope, $state, models, strings) {
|
||||
const vm = this || {};
|
||||
const { instanceGroup, instance } = models;
|
||||
|
||||
vm.mode = 'add';
|
||||
vm.strings = strings;
|
||||
vm.panelTitle = strings.get('state.ADD_BREADCRUMB_LABEL');
|
||||
|
||||
vm.tab = {
|
||||
details: { _active: true },
|
||||
instances: {_disabled: true },
|
||||
jobs: {_disabled: true }
|
||||
};
|
||||
|
||||
vm.form = instanceGroup.createFormSchema('post');
|
||||
|
||||
// Default policy instance percentage value is 0
|
||||
vm.form.policy_instance_percentage._value = 0;
|
||||
|
||||
vm.form.policy_instance_list._lookupTags = true;
|
||||
vm.form.policy_instance_list._model = instance;
|
||||
vm.form.policy_instance_list._placeholder = "Policy Instance List";
|
||||
vm.form.policy_instance_list._resource = 'instances';
|
||||
vm.form.policy_instance_list._route = 'instanceGroups.add.modal.instances';
|
||||
vm.form.policy_instance_list._value = [];
|
||||
|
||||
vm.form.save = data => {
|
||||
data.policy_instance_list = data.policy_instance_list.map(instance => instance.hostname);
|
||||
return instanceGroup.request('post', { data });
|
||||
};
|
||||
|
||||
vm.form.onSaveSuccess = res => {
|
||||
$state.go('instanceGroups.edit', { instance_group_id: res.data.id }, { reload: true });
|
||||
};
|
||||
}
|
||||
|
||||
AddController.$inject = [
|
||||
'$scope',
|
||||
'$state',
|
||||
'resolvedModels',
|
||||
'InstanceGroupsStrings'
|
||||
];
|
||||
|
||||
export default AddController;
|
||||
@@ -0,0 +1,54 @@
|
||||
function EditController ($rootScope, $state, models, strings) {
|
||||
const vm = this || {};
|
||||
const { instanceGroup, instance } = models;
|
||||
|
||||
$rootScope.breadcrumb.instance_group_name = instanceGroup.get('name');
|
||||
|
||||
vm.mode = 'edit';
|
||||
vm.strings = strings;
|
||||
vm.panelTitle = instanceGroup.get('name');
|
||||
|
||||
vm.tab = {
|
||||
details: {
|
||||
_active: true,
|
||||
_go: 'instanceGroups.edit',
|
||||
_params: { instance_group_id: instanceGroup.get('id') }
|
||||
},
|
||||
instances: {
|
||||
_go: 'instanceGroups.instances',
|
||||
_params: { instance_group_id: instanceGroup.get('id') }
|
||||
},
|
||||
jobs: {
|
||||
_go: 'instanceGroups.jobs',
|
||||
_params: { instance_group_id: instanceGroup.get('id') }
|
||||
}
|
||||
};
|
||||
|
||||
vm.form = instanceGroup.createFormSchema('put');
|
||||
|
||||
vm.form.policy_instance_list._lookupTags = true;
|
||||
vm.form.policy_instance_list._model = instance;
|
||||
vm.form.policy_instance_list._placeholder = "Policy Instance List";
|
||||
vm.form.policy_instance_list._resource = 'instances';
|
||||
vm.form.policy_instance_list._route = 'instanceGroups.edit.modal.instances';
|
||||
vm.form.policy_instance_list._value = instanceGroup.get('policy_instance_list');
|
||||
|
||||
vm.form.save = data => {
|
||||
instanceGroup.unset('policy_instance_list');
|
||||
data.policy_instance_list = data.policy_instance_list.map(instance => instance.hostname || instance);
|
||||
return instanceGroup.request('put', { data });
|
||||
};
|
||||
|
||||
vm.form.onSaveSuccess = res => {
|
||||
$state.go('instanceGroups.edit', { instance_group_id: res.data.id }, { reload: true });
|
||||
};
|
||||
}
|
||||
|
||||
EditController.$inject = [
|
||||
'$rootScope',
|
||||
'$state',
|
||||
'resolvedModels',
|
||||
'InstanceGroupsStrings'
|
||||
];
|
||||
|
||||
export default EditController;
|
||||
@@ -0,0 +1,84 @@
|
||||
const templateUrl = require('./instance-list-policy.partial.html');
|
||||
|
||||
function InstanceListPolicyLink (scope, el, attrs, controllers) {
|
||||
const instancePolicyController = controllers[0];
|
||||
const formController = controllers[1];
|
||||
const models = scope.$resolve.resolvedModels;
|
||||
|
||||
instancePolicyController.init(formController, models);
|
||||
}
|
||||
|
||||
|
||||
function InstanceListPolicyController ($scope, $state, strings) {
|
||||
const vm = this || {};
|
||||
let form;
|
||||
let instance;
|
||||
let instanceGroup;
|
||||
|
||||
vm.init = (_form_, _models_) => {
|
||||
form = _form_;
|
||||
({ instance, instanceGroup} = _models_);
|
||||
|
||||
vm.strings = strings;
|
||||
vm.instanceGroupId = instanceGroup.get('id');
|
||||
vm.defaultParams = { page_size: '10', order_by: 'hostname' };
|
||||
|
||||
if (vm.instanceGroupId === undefined) {
|
||||
vm.setInstances();
|
||||
} else {
|
||||
vm.setRelatedInstances();
|
||||
}
|
||||
};
|
||||
|
||||
vm.setInstances = () => {
|
||||
vm.instances = instance.get('results').map(instance => {
|
||||
instance.isSelected = false;
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
vm.setRelatedInstances = () => {
|
||||
vm.instanceGroupName = instanceGroup.get('name');
|
||||
vm.relatedInstances = instanceGroup.get('policy_instance_list');
|
||||
|
||||
vm.instances = instance.get('results').map(instance => {
|
||||
instance.isSelected = vm.relatedInstances.includes(instance.hostname);
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('vm.instances', function() {
|
||||
vm.selectedRows = _.filter(vm.instances, 'isSelected');
|
||||
vm.deselectedRows = _.filter(vm.instances, 'isSelected', false);
|
||||
}, true);
|
||||
|
||||
vm.submit = () => {
|
||||
form.components
|
||||
.filter(component => component.category === 'input')
|
||||
.filter(component => component.state.id === 'policy_instance_list')
|
||||
.forEach(component => {
|
||||
component.state._value = vm.selectedRows;
|
||||
});
|
||||
|
||||
$state.go("^.^");
|
||||
};
|
||||
}
|
||||
|
||||
InstanceListPolicyController.$inject = [
|
||||
'$scope',
|
||||
'$state',
|
||||
'InstanceGroupsStrings'
|
||||
];
|
||||
|
||||
function instanceListPolicy () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: InstanceListPolicyLink,
|
||||
controller: InstanceListPolicyController,
|
||||
controllerAs: 'vm',
|
||||
require: ['instanceListPolicy', '^atForm'],
|
||||
templateUrl
|
||||
};
|
||||
}
|
||||
|
||||
export default instanceListPolicy;
|
||||
@@ -0,0 +1,53 @@
|
||||
<div id="instance-modal" class="modal-dialog">
|
||||
<at-panel on-dismiss="^.^">
|
||||
<at-panel-heading>
|
||||
{{:: vm.strings.get('instance.PANEL_TITLE') }}
|
||||
</at-panel-heading>
|
||||
<multi-select-preview selected-rows='vm.selectedRows' available-rows='vm.instances'></multi-select-preview>
|
||||
<at-panel-body>
|
||||
<div class="at-List-toolbar">
|
||||
<smart-search
|
||||
class="at-List-search"
|
||||
django-model="instances"
|
||||
base-path="instances"
|
||||
iterator="instance"
|
||||
default-params="vm.defaultParams"
|
||||
list="list"
|
||||
dataset="vm.instances"
|
||||
collection="collection"
|
||||
search-tags="searchTags">
|
||||
</smart-search>
|
||||
</div>
|
||||
<at-list results='vm.instances'>
|
||||
<at-row ng-repeat="instance in vm.instances"
|
||||
ng-class="{'at-Row--active': (instance.id === vm.activeId)}">
|
||||
|
||||
<input type="checkbox"
|
||||
class="at-Row-checkbox"
|
||||
ng-model="instance.isSelected"
|
||||
ng-checked="instance.isSelected"
|
||||
ng-attr-tabindex="{{ tab || undefined }}"
|
||||
ng-disabled="state._disabled" />
|
||||
|
||||
<div class="at-Row-items" style="flex: 1">
|
||||
<at-row-item
|
||||
header-value="{{ instance.hostname }}">
|
||||
</at-row-item>
|
||||
</div>
|
||||
</at-row>
|
||||
</at-list>
|
||||
<div class="at-ActionGroup">
|
||||
<div class="pull-right">
|
||||
<button class="btn at-ButtonHollow--default"
|
||||
ng-click="$state.go('^.^')">
|
||||
{{:: vm.strings.get('CANCEL') }}
|
||||
</button>
|
||||
<button class="btn at-Button--success"
|
||||
ng-click="vm.submit()">
|
||||
{{:: vm.strings.get('SAVE') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</at-panel-body>
|
||||
</at-panel>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
.CapacityAdjuster {
|
||||
.at-InputSlider {
|
||||
align-items: center;
|
||||
margin-right: @at-space-4x;
|
||||
}
|
||||
|
||||
.at-InputSlider p {
|
||||
white-space: nowrap;
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
function CapacityAdjuster (templateUrl, ProcessErrors, Wait) {
|
||||
return {
|
||||
scope: {
|
||||
state: '='
|
||||
},
|
||||
templateUrl: templateUrl('instance-groups/capacity-adjuster/capacity-adjuster'),
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
link: function(scope) {
|
||||
const adjustment_values = [{
|
||||
label: 'CPU',
|
||||
value: scope.state.cpu_capacity,
|
||||
},{
|
||||
label: 'RAM',
|
||||
value: scope.state.mem_capacity
|
||||
}];
|
||||
|
||||
scope.min_capacity = _.min(adjustment_values, 'value');
|
||||
scope.max_capacity = _.max(adjustment_values, 'value');
|
||||
|
||||
},
|
||||
controller: function($http) {
|
||||
const vm = this || {};
|
||||
|
||||
vm.slide = (state) => {
|
||||
Wait('start');
|
||||
const data = {
|
||||
"capacity_adjustment": `${state.capacity_adjustment}`
|
||||
};
|
||||
const req = {
|
||||
method: 'PUT',
|
||||
url: state.url,
|
||||
data
|
||||
};
|
||||
$http(req)
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: ' + status
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
Wait('stop');
|
||||
});
|
||||
};
|
||||
},
|
||||
controllerAs: 'vm'
|
||||
};
|
||||
}
|
||||
|
||||
CapacityAdjuster.$inject = [
|
||||
'templateUrl',
|
||||
'ProcessErrors',
|
||||
'Wait'
|
||||
];
|
||||
|
||||
export default CapacityAdjuster;
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="CapacityAdjuster">
|
||||
<div class="at-InputSlider">
|
||||
<p>{{min_capacity.label}} {{min_capacity.value}}</p>
|
||||
<input string-to-number
|
||||
type="range"
|
||||
ng-model="state.capacity_adjustment"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
ng-change="vm.slide(state)"/>
|
||||
<p>{{max_capacity.label}} {{max_capacity.value}}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,21 +1,22 @@
|
||||
capacity-bar {
|
||||
|
||||
width: 50%;
|
||||
margin-right: 25px;
|
||||
min-width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: @at-color-body-background-dark;
|
||||
display: flex;
|
||||
font-size: @at-font-size;
|
||||
min-width: 100px;
|
||||
white-space: nowrap;
|
||||
|
||||
.CapacityBar {
|
||||
background-color: @default-bg;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
height: 10px;
|
||||
border: 1px solid @default-link;
|
||||
width: 100%;
|
||||
border-radius: 100vw;
|
||||
border: 1px solid @default-link;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 10px;
|
||||
margin-right: @at-space-2x;
|
||||
min-width: 100px;
|
||||
overflow: hidden;
|
||||
margin-right: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CapacityBar-remaining {
|
||||
@@ -28,14 +29,21 @@ capacity-bar {
|
||||
}
|
||||
|
||||
.CapacityBar--offline {
|
||||
border-color: @d7grey;
|
||||
color: @at-red;
|
||||
border-color: @at-gray-a9;
|
||||
|
||||
.CapacityBar-remaining {
|
||||
background-color: @d7grey;
|
||||
background-color: @at-gray-b7;
|
||||
}
|
||||
}
|
||||
|
||||
.Capacity-details--percentage {
|
||||
color: @default-data-txt;
|
||||
.Capacity-details--label {
|
||||
margin-right: @at-space-2x;
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.Capacity-details--percentage {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,52 @@
|
||||
export default ['templateUrl', 'ComponentsStrings',
|
||||
function (templateUrl, strings) {
|
||||
return {
|
||||
scope: {
|
||||
capacity: '=',
|
||||
totalCapacity: '='
|
||||
},
|
||||
templateUrl: templateUrl('instance-groups/capacity-bar/capacity-bar'),
|
||||
restrict: 'E',
|
||||
link: function(scope) {
|
||||
scope.isOffline = false;
|
||||
function CapacityBar (templateUrl, strings) {
|
||||
return {
|
||||
scope: {
|
||||
capacity: '=',
|
||||
totalCapacity: '=',
|
||||
labelValue: '@',
|
||||
badge: '='
|
||||
},
|
||||
templateUrl: templateUrl('instance-groups/capacity-bar/capacity-bar'),
|
||||
restrict: 'E',
|
||||
link: function(scope) {
|
||||
scope.isOffline = false;
|
||||
|
||||
scope.$watch('totalCapacity', function(val) {
|
||||
if (val === 0) {
|
||||
scope.isOffline = true;
|
||||
scope.offlineTip = strings.get(`capacityBar.IS_OFFLINE`);
|
||||
} else {
|
||||
scope.isOffline = false;
|
||||
scope.offlineTip = null;
|
||||
}
|
||||
}, true);
|
||||
scope.$watch('totalCapacity', function(val) {
|
||||
if (val === 0) {
|
||||
scope.isOffline = true;
|
||||
scope.labelValue = strings.get(`capacityBar.IS_OFFLINE_LABEL`);
|
||||
scope.offlineTip = strings.get(`capacityBar.IS_OFFLINE`);
|
||||
} else {
|
||||
scope.isOffline = false;
|
||||
scope.offlineTip = null;
|
||||
}
|
||||
}, true);
|
||||
|
||||
scope.$watch('capacity', function() {
|
||||
if (scope.totalCapacity !== 0) {
|
||||
var percentageCapacity = Math
|
||||
.round(scope.capacity / scope.totalCapacity * 1000) / 10;
|
||||
scope.$watch('capacity', function() {
|
||||
if (scope.totalCapacity !== 0) {
|
||||
var percentageCapacity = Math
|
||||
.round(scope.capacity / scope.totalCapacity * 1000) / 10;
|
||||
|
||||
scope.CapacityStyle = {
|
||||
'flex-grow': percentageCapacity * 0.01
|
||||
};
|
||||
scope.CapacityStyle = {
|
||||
'flex-grow': percentageCapacity * 0.01
|
||||
};
|
||||
|
||||
scope.consumedCapacity = `${percentageCapacity}%`;
|
||||
} else {
|
||||
scope.CapacityStyle = {
|
||||
'flex-grow': 1
|
||||
};
|
||||
scope.consumedCapacity = `${percentageCapacity}%`;
|
||||
} else {
|
||||
scope.CapacityStyle = {
|
||||
'flex-grow': 1
|
||||
};
|
||||
|
||||
scope.consumedCapacity = null;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
scope.consumedCapacity = null;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
CapacityBar.$inject = [
|
||||
'templateUrl',
|
||||
'InstanceGroupsStrings'
|
||||
];
|
||||
|
||||
export default CapacityBar;
|
||||
@@ -1,11 +1,20 @@
|
||||
<span class="Capacity-details--label" ng-class="{'CapacityBar--offline': isOffline}">
|
||||
{{ labelValue }}
|
||||
</span>
|
||||
|
||||
<div class="CapacityBar"
|
||||
ng-class="{'CapacityBar--offline': isOffline}"
|
||||
ng-if="!badge"
|
||||
aw-tool-tip="{{ offlineTip }}"
|
||||
data-tip-watch="offlineTip"
|
||||
data-placement="top"
|
||||
data-trigger="hover"
|
||||
data-container="body">
|
||||
<div class="CapacityBar-remaining" ng-style="CapacityStyle"></div>
|
||||
<div class="CapacityBar-consumed"></div>
|
||||
<div class="CapacityBar-remaining" ng-style="CapacityStyle"></div>
|
||||
<div class="CapacityBar-consumed"></div>
|
||||
</div>
|
||||
<span class="Capacity-details--percentage" ng-show="consumedCapacity">{{ consumedCapacity }}</span>
|
||||
|
||||
<span class="Capacity-details--percentage"
|
||||
ng-class="{'badge List-titleBadge': badge}">
|
||||
{{ consumedCapacity }}
|
||||
</span>
|
||||
@@ -1,5 +0,0 @@
|
||||
import capacityBar from './capacity-bar.directive';
|
||||
|
||||
export default
|
||||
angular.module('capacityBarDirective', [])
|
||||
.directive('capacityBar', capacityBar);
|
||||
@@ -1,33 +0,0 @@
|
||||
<div class="Panel">
|
||||
<div class="row Form-tabRow">
|
||||
<div class="col-xs-12">
|
||||
<div class="List-header">
|
||||
<div class="List-title">
|
||||
<div class="List-titleText">{{ instanceGroupName }}</div>
|
||||
</div>
|
||||
<div class="List-details">
|
||||
<div class="Capacity-details">
|
||||
<p class="Capacity-details--label" translate>Used Capacity</p>
|
||||
<capacity-bar capacity="instanceGroupCapacity" total-capacity="instanceGroupTotalCapacity"></capacity-bar>
|
||||
</div>
|
||||
<div class="RunningJobs-details">
|
||||
<p class="RunningJobs-details--label" translate>Running Jobs</p>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ instanceGroupJobsRunning }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="List-exitHolder">
|
||||
<button class="List-exit" ng-click="$state.go('instanceGroups')">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Form-tabHolder">
|
||||
<div class="Form-tab Form-tab--notitle" ng-click="$state.go('instanceGroups.instances.list', {instance_group_id: $stateParams.instance_group_id})" ng-class="{'is-selected': $state.includes('instanceGroups.instances.list')}" translate>INSTANCES</div>
|
||||
<div class="Form-tab Form-tab--notitle" ng-click="$state.go('instanceGroups.instances.jobs', {instance_group_id: $stateParams.instance_group_id})" ng-class="{'is-selected': $state.includes('instanceGroups.instances.jobs')}" translate>JOBS</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ui-view="list"></div>
|
||||
</div>
|
||||
@@ -1,11 +1,13 @@
|
||||
<div class="tab-pane InstanceGroups" id="instance-groups-panel">
|
||||
<aw-limit-panels max-panels="2" panel-container="instance-groups-panel"></aw-limit-panels>
|
||||
<div ui-view="add"></div>
|
||||
<div ui-view="edit"></div>
|
||||
|
||||
<div ui-view="instanceJobs"></div>
|
||||
|
||||
<div ui-view="instances"></div>
|
||||
|
||||
<div ng-cloak id="htmlTemplate" class="Panel">
|
||||
<div ui-view="list"></div>
|
||||
</div>
|
||||
<div ui-view="jobs"></div>
|
||||
|
||||
<div ui-view="list"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import {templateUrl} from '../shared/template-url/template-url.factory';
|
||||
import { N_ } from '../i18n';
|
||||
|
||||
export default {
|
||||
name: 'instanceGroups',
|
||||
url: '/instance_groups',
|
||||
searchPrefix: 'instance_group',
|
||||
ncyBreadcrumb: {
|
||||
label: N_('INSTANCE GROUPS')
|
||||
},
|
||||
params: {
|
||||
instance_group_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
order_by: 'name'
|
||||
},
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
data: {
|
||||
alwaysShowRefreshButton: true,
|
||||
},
|
||||
views: {
|
||||
'@': {
|
||||
templateUrl: templateUrl('./instance-groups/instance-groups'),
|
||||
},
|
||||
'list@instanceGroups': {
|
||||
templateUrl: templateUrl('./instance-groups/list/instance-groups-list'),
|
||||
controller: 'InstanceGroupsList'
|
||||
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
Dataset: ['InstanceGroupList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
30
awx/ui/client/src/instance-groups/instance-groups.strings.js
Normal file
30
awx/ui/client/src/instance-groups/instance-groups.strings.js
Normal file
@@ -0,0 +1,30 @@
|
||||
function InstanceGroupsStrings (BaseString) {
|
||||
BaseString.call(this, 'instanceGroups');
|
||||
|
||||
const { t } = this;
|
||||
const ns = this.instanceGroups;
|
||||
|
||||
ns.state = {
|
||||
ADD_BREADCRUMB_LABEL: t.s('CREATE INSTANCE GROUP'),
|
||||
EDIT_BREADCRUMB_LABEL: t.s('EDIT INSTANCE GROUP')
|
||||
};
|
||||
|
||||
ns.tab = {
|
||||
DETAILS: t.s('DETAILS'),
|
||||
INSTANCES: t.s('INSTANCES'),
|
||||
JOBS: t.s('JOBS')
|
||||
};
|
||||
|
||||
ns.instance = {
|
||||
PANEL_TITLE: t.s('SELECT INSTANCE')
|
||||
};
|
||||
|
||||
ns.capacityBar = {
|
||||
IS_OFFLINE: t.s('Unavailable to run jobs.'),
|
||||
IS_OFFLINE_LABEL: t.s('Unavailable')
|
||||
};
|
||||
}
|
||||
|
||||
InstanceGroupsStrings.$inject = ['BaseStringService'];
|
||||
|
||||
export default InstanceGroupsStrings;
|
||||
@@ -1,41 +0,0 @@
|
||||
import { N_ } from '../../../i18n';
|
||||
|
||||
export default {
|
||||
name: 'instanceGroups.instances.list.job.list',
|
||||
url: '/jobs',
|
||||
searchPrefix: 'instance_job',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'instanceGroups.instances.list',
|
||||
label: N_('{{ breadcrumb.instance_name }}')
|
||||
},
|
||||
params: {
|
||||
instance_job_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
order_by: '-finished',
|
||||
not__launch_type: 'sync'
|
||||
},
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
views: {
|
||||
'list@instanceGroups.instances.list.job': {
|
||||
templateProvider: function(InstanceJobsList, generateList) {
|
||||
let html = generateList.build({
|
||||
list: InstanceJobsList
|
||||
});
|
||||
return html;
|
||||
},
|
||||
controller: 'InstanceJobsController'
|
||||
}
|
||||
},
|
||||
|
||||
resolve: {
|
||||
Dataset: ['InstanceJobsList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = `${GetBasePath('instances')}${$stateParams.instance_id}/jobs`;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
],
|
||||
}
|
||||
};
|
||||
@@ -1,82 +1,89 @@
|
||||
export default ['$scope','InstanceJobsList', 'GetBasePath', 'Rest', 'Dataset','Find', '$state', '$q',
|
||||
function($scope, InstanceJobsList, GetBasePath, Rest, Dataset, Find, $state, $q) {
|
||||
|
||||
let list = InstanceJobsList;
|
||||
function InstanceJobsController ($scope, $filter, $state, model, strings, jobStrings, Instance) {
|
||||
const vm = this || {};
|
||||
let { instance } = model;
|
||||
const instance_id = instance.get('id');
|
||||
|
||||
init();
|
||||
init();
|
||||
|
||||
function init(){
|
||||
$scope.optionsDefer = $q.defer();
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
}
|
||||
function init(){
|
||||
vm.strings = strings;
|
||||
vm.jobStrings = jobStrings;
|
||||
vm.queryset = { page_size: '10', order_by: '-finished'};
|
||||
vm.jobs = instance.get('related.jobs.results');
|
||||
vm.dataset = instance.get('related.jobs');
|
||||
vm.count = instance.get('related.jobs.count');
|
||||
vm.panelTitle = `${jobStrings.get('list.PANEL_TITLE')} | ${instance.get('hostname')}`;
|
||||
|
||||
$scope.$on(`${list.iterator}_options`, function(event, data){
|
||||
$scope.options = data.data.actions.GET;
|
||||
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(){
|
||||
|
||||
if($scope[list.name] && $scope[list.name].length > 0) {
|
||||
$scope[list.name].forEach(function(item, item_idx) {
|
||||
var itm = $scope[list.name][item_idx];
|
||||
|
||||
if(item.summary_fields && item.summary_fields.source_workflow_job &&
|
||||
item.summary_fields.source_workflow_job.id){
|
||||
item.workflow_result_link = `/#/workflows/${item.summary_fields.source_workflow_job.id}`;
|
||||
}
|
||||
|
||||
// Set the item type label
|
||||
if (list.fields.type && $scope.options &&
|
||||
$scope.options.hasOwnProperty('type')) {
|
||||
$scope.options.type.choices.forEach(function(choice) {
|
||||
if (choice[0] === item.type) {
|
||||
itm.type_label = choice[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
buildTooltips(itm);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildTooltips(job) {
|
||||
job.status_tip = 'Job ' + job.status + ". Click for details.";
|
||||
}
|
||||
|
||||
$scope.viewjobResults = function(job) {
|
||||
var goTojobResults = function(state) {
|
||||
$state.go(state, { id: job.id }, { reload: true });
|
||||
};
|
||||
switch (job.type) {
|
||||
case 'job':
|
||||
goTojobResults('jobResult');
|
||||
break;
|
||||
case 'ad_hoc_command':
|
||||
goTojobResults('adHocJobStdout');
|
||||
break;
|
||||
case 'system_job':
|
||||
goTojobResults('managementJobStdout');
|
||||
break;
|
||||
case 'project_update':
|
||||
goTojobResults('scmUpdateStdout');
|
||||
break;
|
||||
case 'inventory_update':
|
||||
goTojobResults('inventorySyncStdout');
|
||||
break;
|
||||
case 'workflow_job':
|
||||
goTojobResults('workflowResults');
|
||||
break;
|
||||
}
|
||||
vm.tab = {
|
||||
details: {_hide: true},
|
||||
instances: {_hide: true},
|
||||
jobs: {_hide: true}
|
||||
};
|
||||
|
||||
$scope.$watchCollection(`${$scope.list.name}`, function() {
|
||||
optionsRequestDataProcessing();
|
||||
}
|
||||
);
|
||||
}
|
||||
];
|
||||
|
||||
vm.getTime = function(time) {
|
||||
let val = "";
|
||||
if (time) {
|
||||
val += $filter('longDate')(time);
|
||||
}
|
||||
if (val === "") {
|
||||
val = undefined;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
$scope.isSuccessful = function (status) {
|
||||
return (status === "successful");
|
||||
};
|
||||
|
||||
$scope.viewjobResults = function(job) {
|
||||
var goTojobResults = function(state) {
|
||||
$state.go(state, { id: job.id }, { reload: true });
|
||||
};
|
||||
switch (job.type) {
|
||||
case 'job':
|
||||
goTojobResults('jobResult');
|
||||
break;
|
||||
case 'ad_hoc_command':
|
||||
goTojobResults('adHocJobStdout');
|
||||
break;
|
||||
case 'system_job':
|
||||
goTojobResults('managementJobStdout');
|
||||
break;
|
||||
case 'project_update':
|
||||
goTojobResults('scmUpdateStdout');
|
||||
break;
|
||||
case 'inventory_update':
|
||||
goTojobResults('inventorySyncStdout');
|
||||
break;
|
||||
case 'workflow_job':
|
||||
goTojobResults('workflowResults');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$on('ws-jobs', () => {
|
||||
new Instance(['get', 'options'], [instance_id, instance_id])
|
||||
.then((data) => {
|
||||
return data.extend('get', 'jobs', {params: {page_size: "10", order_by: "-finished"}});
|
||||
})
|
||||
.then((data) => {
|
||||
instance = data;
|
||||
init();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
InstanceJobsController.$inject = [
|
||||
'$scope',
|
||||
'$filter',
|
||||
'$state',
|
||||
'resolvedModels',
|
||||
'InstanceGroupsStrings',
|
||||
'JobStrings',
|
||||
'InstanceModel'
|
||||
];
|
||||
|
||||
export default InstanceJobsController;
|
||||
@@ -1,78 +0,0 @@
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
name: 'instance_jobs',
|
||||
iterator: 'instance_job',
|
||||
index: false,
|
||||
hover: false,
|
||||
well: false,
|
||||
emptyListText: i18n._('No jobs have yet run.'),
|
||||
title: false,
|
||||
basePath: 'api/v2/instances/{{$stateParams.instance_id}}/jobs',
|
||||
|
||||
fields: {
|
||||
status: {
|
||||
label: '',
|
||||
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus',
|
||||
dataTipWatch: 'instance_job.status_tip',
|
||||
awToolTip: "{{ instance_job.status_tip }}",
|
||||
awTipPlacement: "right",
|
||||
dataTitle: "{{ instance_job.status_popover_title }}",
|
||||
icon: 'icon-job-{{ instance_job.status }}',
|
||||
iconOnly: true,
|
||||
ngClick:"viewjobResults(instance_job)",
|
||||
nosort: true
|
||||
},
|
||||
id: {
|
||||
label: i18n._('ID'),
|
||||
ngClick:"viewjobResults(instance_job)",
|
||||
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
|
||||
awToolTip: "{{ instance_job.status_tip }}",
|
||||
dataPlacement: 'top',
|
||||
noLink: true
|
||||
},
|
||||
name: {
|
||||
label: i18n._('Name'),
|
||||
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6',
|
||||
ngClick: "viewjobResults(instance_job)",
|
||||
nosort: true,
|
||||
badgePlacement: 'right',
|
||||
badgeCustom: true,
|
||||
badgeIcon: `<a href="{{ instance_job.workflow_result_link }}"
|
||||
aw-tool-tip="{{'View workflow results'|translate}}"
|
||||
data-placement="top"
|
||||
data-original-title="" title="">
|
||||
<i class="WorkflowBadge"
|
||||
ng-show="instance_job.launch_type === 'workflow' ">
|
||||
W
|
||||
</i>
|
||||
</a>`
|
||||
},
|
||||
type: {
|
||||
label: i18n._('Type'),
|
||||
ngBind: 'instance_job.type_label',
|
||||
link: false,
|
||||
columnClass: "col-lg-2 hidden-md hidden-sm hidden-xs",
|
||||
nosort: true
|
||||
},
|
||||
finished: {
|
||||
label: i18n._('Finished'),
|
||||
noLink: true,
|
||||
filter: "longDate",
|
||||
columnClass: "col-lg-2 col-md-3 col-sm-3 hidden-xs",
|
||||
key: true,
|
||||
desc: true,
|
||||
nosort: true
|
||||
},
|
||||
labels: {
|
||||
label: i18n._('Labels'),
|
||||
type: 'labels',
|
||||
nosort: true,
|
||||
showDelete: false,
|
||||
columnClass: 'List-tableCell col-lg-4 col-md-4 hidden-sm hidden-xs',
|
||||
sourceModel: 'labels',
|
||||
sourceField: 'name',
|
||||
},
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -1,32 +0,0 @@
|
||||
<div class="Panel">
|
||||
<div class="row Form-tabRow">
|
||||
<div class="col-xs-12">
|
||||
<div class="List-header">
|
||||
<div class="List-title">
|
||||
<div class="List-titleText">{{ instanceName }}</div>
|
||||
</div>
|
||||
<div class="List-details">
|
||||
<div class="Capacity-details">
|
||||
<p class="Capacity-details--label" translate>Used Capacity</p>
|
||||
<capacity-bar capacity="instanceCapacity" total-capacity="instanceTotalCapacity"></capacity-bar>
|
||||
</div>
|
||||
<div class="RunningJobs-details">
|
||||
<p class="RunningJobs-details--label" translate>Running Jobs</p>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ instanceJobsRunning }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="List-exitHolder">
|
||||
<button class="List-exit" ng-click="$state.go('instanceGroups.instances.list')">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Form-tabHolder">
|
||||
<div class="Form-tab Form-tab--notitle is-selected" translate>JOBS</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="instance-jobs-list" ui-view="list"></div>
|
||||
</div>
|
||||
@@ -1,38 +0,0 @@
|
||||
import { templateUrl } from '../../../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'instanceGroups.instances.list.job',
|
||||
url: '/:instance_id',
|
||||
abstract: true,
|
||||
ncyBreadcrumb: {
|
||||
skip: true
|
||||
},
|
||||
views: {
|
||||
'instanceJobs@instanceGroups': {
|
||||
templateUrl: templateUrl('./instance-groups/instances/instance-jobs/instance-jobs'),
|
||||
controller: function($scope, $rootScope, instance) {
|
||||
$scope.instanceName = instance.hostname;
|
||||
$scope.instanceCapacity = instance.consumed_capacity;
|
||||
$scope.instanceTotalCapacity = instance.capacity;
|
||||
$scope.instanceJobsRunning = instance.jobs_running;
|
||||
$rootScope.breadcrumb.instance_name = instance.hostname;
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
instance: ['GetBasePath', 'Rest', 'ProcessErrors', '$stateParams', function(GetBasePath, Rest, ProcessErrors, $stateParams) {
|
||||
let url = GetBasePath('instances') + $stateParams.instance_id;
|
||||
Rest.setUrl(url);
|
||||
return Rest.get()
|
||||
.then(({data}) => {
|
||||
return data;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(null, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to get instance groups info. GET returned status: ' + status
|
||||
});
|
||||
});
|
||||
}]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
.Modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
background: #000;
|
||||
z-index: 2;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.Modal-holder {
|
||||
position: fixed;
|
||||
top: 1;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
z-index: 3;
|
||||
|
||||
.modal-dialog {
|
||||
padding-top: 100px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
function InstanceModalController ($scope, $state, models, strings, ProcessErrors, Wait) {
|
||||
const { instance, instanceGroup } = models;
|
||||
const vm = this || {};
|
||||
|
||||
vm.setInstances = () => {
|
||||
vm.instances = instance.get('results').map(instance => {
|
||||
instance.isSelected = false;
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
vm.setRelatedInstances = () => {
|
||||
vm.instanceGroupName = instanceGroup.get('name');
|
||||
vm.relatedInstances = instanceGroup.get('related.instances.results');
|
||||
vm.relatedInstanceIds = vm.relatedInstances.map(instance => instance.id);
|
||||
vm.instances = instance.get('results').map(instance => {
|
||||
instance.isSelected = vm.relatedInstanceIds.includes(instance.id);
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
vm.strings = strings;
|
||||
vm.panelTitle = strings.get('instance.PANEL_TITLE');
|
||||
vm.instanceGroupId = instanceGroup.get('id');
|
||||
|
||||
if (vm.instanceGroupId === undefined) {
|
||||
vm.setInstances();
|
||||
} else {
|
||||
vm.setRelatedInstances();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('vm.instances', function() {
|
||||
vm.selectedRows = _.filter(vm.instances, 'isSelected');
|
||||
vm.deselectedRows = _.filter(vm.instances, 'isSelected', false);
|
||||
}, true);
|
||||
|
||||
vm.submit = () => {
|
||||
Wait('start');
|
||||
const associate = vm.selectedRows
|
||||
.map(instance => ({id: instance.id}));
|
||||
const disassociate = vm.deselectedRows
|
||||
.map(instance => ({id: instance.id, disassociate: true}));
|
||||
|
||||
const all = associate.concat(disassociate);
|
||||
const defers = all.map((data) => {
|
||||
const config = {
|
||||
url: `${vm.instanceGroupId}/instances/`,
|
||||
data: data
|
||||
};
|
||||
return instanceGroup.http.post(config);
|
||||
});
|
||||
|
||||
Promise.all(defers)
|
||||
.then(vm.onSaveSuccess)
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: ' + status
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
Wait('stop');
|
||||
});
|
||||
};
|
||||
|
||||
vm.onSaveSuccess = () => {
|
||||
$state.go('instanceGroups.instances', {}, {reload: 'instanceGroups.instances'});
|
||||
};
|
||||
}
|
||||
|
||||
InstanceModalController.$inject = [
|
||||
'$scope',
|
||||
'$state',
|
||||
'resolvedModels',
|
||||
'InstanceGroupsStrings',
|
||||
'ProcessErrors',
|
||||
'Wait'
|
||||
];
|
||||
|
||||
export default InstanceModalController;
|
||||
@@ -0,0 +1,55 @@
|
||||
<div id="instance-modal" class="modal-dialog">
|
||||
<at-panel on-dismiss="instanceGroups.instances">
|
||||
<at-panel-heading>
|
||||
{{ vm.panelTitle }} | {{ vm.instanceGroupName}}
|
||||
</at-panel-heading>
|
||||
<multi-select-preview selected-rows='vm.selectedRows' available-rows='vm.instances'></multi-select-preview>
|
||||
<at-panel-body>
|
||||
<div class="at-List-toolbar">
|
||||
<smart-search
|
||||
class="at-List-search"
|
||||
django-model="instances"
|
||||
base-path="instances"
|
||||
iterator="instance"
|
||||
list="list"
|
||||
dataset="vm.instances"
|
||||
collection="collection"
|
||||
search-tags="searchTags">
|
||||
</smart-search>
|
||||
</div>
|
||||
<at-list results='vm.instances'>
|
||||
<at-row ng-repeat="instance in vm.instances"
|
||||
ng-class="{'at-Row--active': (instance.id === vm.activeId)}">
|
||||
|
||||
<input type="checkbox"
|
||||
class="at-Row-checkbox"
|
||||
ng-class="{ 'at-Input--rejected': state.rejected }"
|
||||
ng-model="instance.isSelected"
|
||||
ng-checked="instance.isSelected"
|
||||
ng-attr-tabindex="{{ tab || undefined }}"
|
||||
ng-disabled="state._disabled" />
|
||||
|
||||
<div class="at-Row-items" style="flex: 1">
|
||||
<at-row-item
|
||||
header-value="{{ instance.hostname }}"
|
||||
header-tag="{{ vm.instanceTypes[instance.type] }}"></at-row-item>
|
||||
</at-row-item>
|
||||
</div>
|
||||
</at-row>
|
||||
|
||||
</at-list>
|
||||
<div class="at-ActionGroup">
|
||||
<div class="pull-right">
|
||||
<button class="btn at-ButtonHollow--default"
|
||||
ng-click="$state.go('instanceGroups.instances')">
|
||||
{{:: vm.strings.get('CANCEL') }}
|
||||
</button>
|
||||
<button class="btn at-Button--success"
|
||||
ng-click="vm.submit()">
|
||||
{{:: vm.strings.get('SAVE') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</at-panel-body>
|
||||
</at-panel>
|
||||
</div>
|
||||
@@ -1,44 +1,73 @@
|
||||
<div class="instances-list">
|
||||
<smart-search django-model="instances" base-path="instances" iterator="instance" dataset="instance_dataset"
|
||||
list="list" collection="instances" search-tags="searchTags">
|
||||
</smart-search>
|
||||
<at-panel>
|
||||
<at-panel-heading>
|
||||
{{ vm.panelTitle }}
|
||||
</at-panel-heading>
|
||||
|
||||
<div class="List-noItems ng-hide" ng-show="instances.length === 0 && (searchTags | isEmpty)" translate>PLEASE ADD ITEMS TO THIS LIST</div>
|
||||
<div class="list-table-container" ng-show="instances.length > 0">
|
||||
<table id="instances_table" class="List-table" is-extended="false">
|
||||
<thead>
|
||||
<tr class="List-tableHeaderRow">
|
||||
<th id="instance-hostname-header" class="List-tableHeader list-header col-md-5 col-sm-5 col-xs-5" ng-click="columnNoSort !== 'true' && toggleColumnOrderBy()"
|
||||
ng-class="{'list-header-noSort' : columnNoSort === 'true'}" base-path="instances" collection="instances"
|
||||
dataset="instance_dataset" column-sort="" column-field="hostname" column-iterator="instance" column-no-sort="undefined"
|
||||
column-label="Name" column-custom-class="col-md-5 col-sm-5 col-xs-5" query-set="instance_queryset">
|
||||
"{{'Name' | translate}}"
|
||||
<i ng-if="columnNoSort !== 'true'" class="fa columnSortIcon fa-sort-up" ng-class="orderByIcon()"></i>
|
||||
</th>
|
||||
<th id="instance-jobs_running-header" class="List-tableHeader list-header list-header-noSort" translate>
|
||||
Running Jobs
|
||||
</th>
|
||||
<th id="instance-consumed_capacity-header" class="List-tableHeader list-header list-header-noSort" translate>
|
||||
Used Capacity
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ngRepeat: instance in instances -->
|
||||
<tr ng-class="{isActive: isActive(instance.id)}" id="instance.id" class="List-tableRow instance_class ng-scope" ng-repeat="instance in instances">
|
||||
<td class="List-tableCell hostname-column col-md-5 col-sm-5 col-xs-5">
|
||||
<a ui-sref="instanceGroups.instances.list.job.list({instance_id: instance.id})" class="ng-binding">{{ instance.hostname }}</a>
|
||||
</td>
|
||||
<td class="List-tableCell jobs_running-column ng-binding">
|
||||
<a ui-sref="instanceGroups.instances.jobs({instance_group_id: $stateParams.instance_group_id})">
|
||||
{{ instance.jobs_running }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="List-tableCell List-tableCell--capacityColumn ng-binding">
|
||||
<capacity-bar capacity="instance.consumed_capacity" total-capacity="instance.capacity">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<at-tab-group>
|
||||
<at-tab state="vm.tab.details">{{:: vm.strings.get('tab.DETAILS') }}</at-tab>
|
||||
<at-tab state="vm.tab.instances">{{:: vm.strings.get('tab.INSTANCES') }}</at-tab>
|
||||
<at-tab state="vm.tab.jobs" ng-hide="$state.current.name === 'instanceGroups.add'">{{:: vm.strings.get('tab.JOBS') }}</at-tab>
|
||||
</at-tab-group>
|
||||
|
||||
<at-panel-body>
|
||||
<div class="at-List-toolbar">
|
||||
<smart-search
|
||||
class="at-List-search"
|
||||
django-model="instances"
|
||||
base-path="{{list.basePath}}"
|
||||
iterator="instance"
|
||||
list="list"
|
||||
dataset="instance_dataset"
|
||||
collection="collection"
|
||||
search-tags="searchTags">
|
||||
</smart-search>
|
||||
|
||||
<div class="at-List-toolbarAction">
|
||||
<button
|
||||
type="button"
|
||||
ng-click="$state.go('instanceGroups.instances.modal.add')"
|
||||
class="at-Button--add"
|
||||
aria-expanded="false">
|
||||
</button>
|
||||
<div ui-view="modal"></div>
|
||||
</div>
|
||||
</div>
|
||||
<at-list results='vm.instances'>
|
||||
<at-row ng-repeat="instance in vm.instances"
|
||||
ng-class="{'at-Row--active': (instance.id === vm.activeId)}">
|
||||
<div class="at-Row-firstColumn">
|
||||
<div class="ScheduleToggle"
|
||||
ng-class="{'is-on': instance.enabled}">
|
||||
<button ng-show="instance.enabled"
|
||||
class="ScheduleToggle-switch is-on ng-hide"
|
||||
ng-click="vm.toggle(instance)">
|
||||
{{:: vm.strings.get('ON') }}
|
||||
</button>
|
||||
<button ng-show="!instance.enabled"
|
||||
class="ScheduleToggle-switch"
|
||||
ng-click="vm.toggle(instance)">
|
||||
{{:: vm.strings.get('OFF') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="at-Row-items">
|
||||
<at-row-item header-value="{{ instance.hostname }}"></at-row-item>
|
||||
<div class="at-Row--rowLayout">
|
||||
<at-row-item
|
||||
label-value="Running Jobs"
|
||||
label-state="instanceGroups.instanceJobs({instance_group_id: {{vm.instance_group_id}}, instance_id: {{instance.id}}})"
|
||||
value="{{ instance.jobs_running }}"
|
||||
badge="true">
|
||||
</at-row-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="at-Row-actions">
|
||||
<capacity-adjuster state="instance"></capacity-adjuster>
|
||||
<capacity-bar label-value="Used Capacity" capacity="instance.consumed_capacity" total-capacity="instance.capacity"></capacity-bar>
|
||||
</div>
|
||||
</at-row>
|
||||
</at-list>
|
||||
</at-panel-body>
|
||||
</at-panel>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
import { N_ } from '../../i18n';
|
||||
|
||||
export default {
|
||||
name: 'instanceGroups.instances.list',
|
||||
url: '/instances',
|
||||
searchPrefix: 'instance',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'instanceGroups',
|
||||
label: N_('{{breadcrumb.instance_group_name}}')
|
||||
},
|
||||
params: {
|
||||
instance_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
order_by: 'hostname'
|
||||
},
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
views: {
|
||||
'list@instanceGroups.instances': {
|
||||
templateUrl: templateUrl('./instance-groups/instances/instances-list'),
|
||||
controller: 'InstanceListController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
Dataset: ['InstanceList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = `${GetBasePath('instance_groups')}${$stateParams.instance_group_id}/instances`;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -1,20 +1,97 @@
|
||||
export default ['$scope', 'InstanceList', 'GetBasePath', 'Rest', 'Dataset','Find', '$state', '$q',
|
||||
function($scope, InstanceList, GetBasePath, Rest, Dataset, Find, $state, $q) {
|
||||
let list = InstanceList;
|
||||
function InstancesController ($scope, $state, $http, models, Instance, strings, Dataset, ProcessErrors) {
|
||||
const { instanceGroup } = models;
|
||||
const vm = this || {};
|
||||
vm.strings = strings;
|
||||
vm.panelTitle = instanceGroup.get('name');
|
||||
vm.instances = instanceGroup.get('related.instances.results');
|
||||
vm.instance_group_id = instanceGroup.get('id');
|
||||
|
||||
init();
|
||||
init();
|
||||
|
||||
function init(){
|
||||
$scope.optionsDefer = $q.defer();
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
}
|
||||
|
||||
$scope.isActive = function(id) {
|
||||
let selected = parseInt($state.params.instance_id);
|
||||
return id === selected;
|
||||
function init() {
|
||||
$scope.list = {
|
||||
name: 'instances',
|
||||
iterator: 'instance',
|
||||
basePath: `/api/v2/instance_groups/${vm.instance_group_id}/instances/`
|
||||
};
|
||||
|
||||
$scope.collection = {
|
||||
iterator: 'instance',
|
||||
basePath: `/api/v2/instance_groups/${vm.instance_group_id}/instances/`
|
||||
};
|
||||
|
||||
$scope[`${$scope.list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[$scope.list.name] = $scope[`${$scope.list.iterator}_dataset`].results;
|
||||
$scope.instances = vm.instances;
|
||||
|
||||
$scope.$on('updateDataset', function(e, dataset) {
|
||||
$scope[`${$scope.list.iterator}_dataset`] = dataset;
|
||||
$scope[$scope.list.name] = dataset.results;
|
||||
vm.instances = dataset.results;
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
vm.tab = {
|
||||
details: {
|
||||
_go: 'instanceGroups.edit',
|
||||
_params: { instance_group_id: vm.instance_group_id }
|
||||
},
|
||||
instances: {
|
||||
_active: true,
|
||||
_go: 'instanceGroups.instances',
|
||||
_params: { instance_group_id: vm.instance_group_id }
|
||||
},
|
||||
jobs: {
|
||||
_go: 'instanceGroups.jobs',
|
||||
_params: { instance_group_id: vm.instance_group_id }
|
||||
}
|
||||
};
|
||||
|
||||
vm.toggle = (toggled) => {
|
||||
const instance = _.find(vm.instances, 'id', toggled.id);
|
||||
instance.enabled = !instance.enabled;
|
||||
|
||||
const data = {
|
||||
"capacity_adjustment": instance.capacity_adjustment,
|
||||
"enabled": instance.enabled
|
||||
};
|
||||
|
||||
const req = {
|
||||
method: 'PUT',
|
||||
url: instance.url,
|
||||
data
|
||||
};
|
||||
|
||||
$http(req).then(vm.onSaveSuccess)
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: ' + status
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
vm.onSaveSuccess = () => {
|
||||
$state.transitionTo($state.current, $state.params, {
|
||||
reload: true, location: true, inherit: false, notify: true
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isActive = function(id) {
|
||||
let selected = parseInt($state.params.instance_id);
|
||||
return id === selected;
|
||||
};
|
||||
}
|
||||
|
||||
InstancesController.$inject = [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$http',
|
||||
'resolvedModels',
|
||||
'InstanceModel',
|
||||
'InstanceGroupsStrings',
|
||||
'Dataset',
|
||||
'ProcessErrors'
|
||||
];
|
||||
|
||||
export default InstancesController;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
name: 'instances' ,
|
||||
iterator: 'instance',
|
||||
listTitle: false,
|
||||
index: false,
|
||||
hover: false,
|
||||
tabs: true,
|
||||
well: true,
|
||||
|
||||
fields: {
|
||||
hostname: {
|
||||
key: true,
|
||||
label: i18n._('Name'),
|
||||
columnClass: 'col-md-3 col-sm-9 col-xs-9',
|
||||
modalColumnClass: 'col-md-8',
|
||||
uiSref: 'instanceGroups.instances.list.job({instance_id: instance.id})'
|
||||
},
|
||||
consumed_capacity: {
|
||||
label: i18n._('Capacity'),
|
||||
nosort: true,
|
||||
},
|
||||
jobs_running: {
|
||||
label: i18n._('Running Jobs'),
|
||||
nosort: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -1,35 +0,0 @@
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'instanceGroups.instances',
|
||||
url: '/:instance_group_id',
|
||||
abstract: true,
|
||||
views: {
|
||||
'instances@instanceGroups': {
|
||||
templateUrl: templateUrl('./instance-groups/instance-group'),
|
||||
controller: function($scope, $rootScope, instanceGroup) {
|
||||
$scope.instanceGroupName = instanceGroup.name;
|
||||
$scope.instanceGroupCapacity = instanceGroup.consumed_capacity;
|
||||
$scope.instanceGroupTotalCapacity = instanceGroup.capacity;
|
||||
$scope.instanceGroupJobsRunning = instanceGroup.jobs_running;
|
||||
$rootScope.breadcrumb.instance_group_name = instanceGroup.name;
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
instanceGroup: ['GetBasePath', 'Rest', 'ProcessErrors', '$stateParams', function(GetBasePath, Rest, ProcessErrors, $stateParams) {
|
||||
let url = GetBasePath('instance_groups') + $stateParams.instance_group_id;
|
||||
Rest.setUrl(url);
|
||||
return Rest.get()
|
||||
.then(({data}) => {
|
||||
return data;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(null, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to get instance groups info. GET returned status: ' + status
|
||||
});
|
||||
});
|
||||
}]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
<at-panel>
|
||||
<at-panel-heading>
|
||||
{{ vm.panelTitle }}
|
||||
</at-panel-heading>
|
||||
<at-tab-group>
|
||||
<at-tab state="vm.tab.details">{{:: vm.strings.get('tab.DETAILS') }}</at-tab>
|
||||
<at-tab state="vm.tab.instances">{{:: vm.strings.get('tab.INSTANCES') }}</at-tab>
|
||||
<at-tab state="vm.tab.jobs">{{:: vm.strings.get('tab.JOBS') }}</at-tab>
|
||||
</at-tab-group>
|
||||
|
||||
<at-panel-body>
|
||||
<div class="at-List-toolbar">
|
||||
<smart-search
|
||||
class="at-List-search"
|
||||
django-model="jobs"
|
||||
base-path="unified_jobs"
|
||||
iterator="job"
|
||||
list="list"
|
||||
dataset="job_dataset"
|
||||
collection="collection"
|
||||
search-tags="searchTags"
|
||||
query-set="querySet">
|
||||
</smart-search>
|
||||
</div>
|
||||
<at-list results="vm.jobs">
|
||||
<at-row ng-repeat="job in vm.jobs"
|
||||
ng-class="{'at-Row--active': (job.id === vm.activeId)}"
|
||||
job-id="{{ job.id }}">
|
||||
<div class="at-RowStatus">
|
||||
<a ng-click="vm.viewJobResults(job)" aw-tool-tip="{{ job.status_tip }}" data-tip-watch="job.status_tip" aw-tip-placement="right" >
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="at-Row-items">
|
||||
<at-row-item
|
||||
class="at-RowItem--isHeaderLink"
|
||||
header-value="{{ job.name }}"
|
||||
header-tag="{{ job.type }}"
|
||||
ng-click="vm.viewjobResults(job)">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_STARTED') }}"
|
||||
value="{{ vm.getTime(job.started) }}">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_FINISHED') }}"
|
||||
value="{{ vm.getTime(job.finished) }}">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
header-value="{{ job.name }}"
|
||||
header-link="/#/jobs/workflow_job/{{ job.id }}"
|
||||
header-tag="{{ vm.jobTypes[job.type] }}"
|
||||
ng-if="job.type === 'workflow_job_job'">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_TEMPLATE') }}"
|
||||
value="{{ job.summary_fields.job_template.name }}"
|
||||
value-link="/#/templates/job_template/{{ job.summary_fields.job_template.id }}">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_INVENTORY') }}"
|
||||
value="{{ job.summary_fields.inventory.name }}"
|
||||
value-link="/#/inventories/inventory/{{ job.summary_fields.inventory.id }}">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_PROJECT') }}"
|
||||
value="{{ job.summary_fields.project.name }}"
|
||||
value-link="/#/projects/{{ job.summary_fields.project.id }}">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_CREDENTIALS') }}"
|
||||
tag-values="job.summary_fields.credentials"
|
||||
tags-are-creds="true">
|
||||
</at-row-item>
|
||||
</div>
|
||||
</at-row>
|
||||
</at-list>
|
||||
<paginate
|
||||
collection="vm.jobs"
|
||||
dataset="vm.dataset"
|
||||
iterator="job"
|
||||
base-path="unified_jobs"
|
||||
query-set="vm.queryset">
|
||||
</paginate>
|
||||
</at-panel-body>
|
||||
</at-panel>
|
||||
@@ -1,41 +0,0 @@
|
||||
import { N_ } from '../../i18n';
|
||||
|
||||
export default {
|
||||
name: 'instanceGroups.instances.jobs',
|
||||
url: '/jobs',
|
||||
searchPrefix: 'job',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'instanceGroups.instances.list',
|
||||
label: N_('JOBS')
|
||||
},
|
||||
params: {
|
||||
job_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
order_by: '-finished',
|
||||
not__launch_type: 'sync'
|
||||
},
|
||||
dynamic: true
|
||||
},
|
||||
instance_group_id: null
|
||||
},
|
||||
views: {
|
||||
'list@instanceGroups.instances': {
|
||||
templateProvider: function(JobsList, generateList) {
|
||||
let html = generateList.build({
|
||||
list: JobsList
|
||||
});
|
||||
return html;
|
||||
},
|
||||
controller: 'JobsListController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
Dataset: ['JobsList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = `${GetBasePath('instance_groups')}${$stateParams.instance_group_id}/jobs`;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -1,82 +1,100 @@
|
||||
export default ['$scope','JobsList', 'GetBasePath', 'Rest', 'Dataset','Find', '$state', '$q',
|
||||
function($scope, JobsList, GetBasePath, Rest, Dataset, Find, $state, $q) {
|
||||
|
||||
let list = JobsList;
|
||||
function InstanceGroupJobsController ($scope, $filter, $state, model, strings, jobStrings, InstanceGroup) {
|
||||
const vm = this || {};
|
||||
let { instanceGroup } = model;
|
||||
const instance_group_id = instanceGroup.get('id');
|
||||
|
||||
init();
|
||||
init();
|
||||
|
||||
function init(){
|
||||
$scope.optionsDefer = $q.defer();
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
}
|
||||
function init(){
|
||||
vm.strings = strings;
|
||||
vm.jobStrings = jobStrings;
|
||||
vm.queryset = { page_size: '10', order_by: '-finished', instance_group_id: instance_group_id };
|
||||
vm.jobs = instanceGroup.get('related.jobs.results');
|
||||
vm.dataset = instanceGroup.get('related.jobs');
|
||||
vm.count = instanceGroup.get('related.jobs.count');
|
||||
vm.panelTitle = instanceGroup.get('name');
|
||||
|
||||
$scope.$on(`${list.iterator}_options`, function(event, data){
|
||||
$scope.options = data.data.actions.GET;
|
||||
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(){
|
||||
|
||||
if($scope[list.name] && $scope[list.name].length > 0) {
|
||||
$scope[list.name].forEach(function(item, item_idx) {
|
||||
var itm = $scope[list.name][item_idx];
|
||||
if(item.summary_fields && item.summary_fields.source_workflow_job &&
|
||||
item.summary_fields.source_workflow_job.id){
|
||||
item.workflow_result_link = `/#/workflows/${item.summary_fields.source_workflow_job.id}`;
|
||||
}
|
||||
|
||||
// Set the item type label
|
||||
if (list.fields.type && $scope.options &&
|
||||
$scope.options.hasOwnProperty('type')) {
|
||||
$scope.options.type.choices.forEach(function(choice) {
|
||||
if (choice[0] === item.type) {
|
||||
itm.type_label = choice[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
buildTooltips(itm);
|
||||
});
|
||||
vm.tab = {
|
||||
details: {
|
||||
_go: 'instanceGroups.edit',
|
||||
_params: { instance_group_id },
|
||||
_label: strings.get('tab.DETAILS')
|
||||
},
|
||||
instances: {
|
||||
_go: 'instanceGroups.instances',
|
||||
_params: { instance_group_id },
|
||||
_label: strings.get('tab.INSTANCES')
|
||||
},
|
||||
jobs: {
|
||||
_active: true,
|
||||
_label: strings.get('tab.JOBS')
|
||||
}
|
||||
}
|
||||
|
||||
function buildTooltips(job) {
|
||||
job.status_tip = 'Job ' + job.status + ". Click for details.";
|
||||
}
|
||||
|
||||
$scope.viewjobResults = function(job) {
|
||||
var goTojobResults = function(state) {
|
||||
$state.go(state, { id: job.id }, { reload: true });
|
||||
};
|
||||
switch (job.type) {
|
||||
case 'job':
|
||||
goTojobResults('jobResult');
|
||||
break;
|
||||
case 'ad_hoc_command':
|
||||
goTojobResults('adHocJobStdout');
|
||||
break;
|
||||
case 'system_job':
|
||||
goTojobResults('managementJobStdout');
|
||||
break;
|
||||
case 'project_update':
|
||||
goTojobResults('scmUpdateStdout');
|
||||
break;
|
||||
case 'inventory_update':
|
||||
goTojobResults('inventorySyncStdout');
|
||||
break;
|
||||
case 'workflow_job':
|
||||
goTojobResults('workflowResults');
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.$watchCollection(`${$scope.list.name}`, function() {
|
||||
optionsRequestDataProcessing();
|
||||
}
|
||||
);
|
||||
}
|
||||
];
|
||||
|
||||
vm.getTime = function(time) {
|
||||
let val = "";
|
||||
if (time) {
|
||||
val += $filter('longDate')(time);
|
||||
}
|
||||
if (val === "") {
|
||||
val = undefined;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
$scope.isSuccessful = function (status) {
|
||||
return (status === "successful");
|
||||
};
|
||||
|
||||
vm.viewjobResults = function(job) {
|
||||
var goTojobResults = function(state) {
|
||||
$state.go(state, { id: job.id }, { reload: true });
|
||||
};
|
||||
switch (job.type) {
|
||||
case 'job':
|
||||
goTojobResults('jobResult');
|
||||
break;
|
||||
case 'ad_hoc_command':
|
||||
goTojobResults('adHocJobStdout');
|
||||
break;
|
||||
case 'system_job':
|
||||
goTojobResults('managementJobStdout');
|
||||
break;
|
||||
case 'project_update':
|
||||
goTojobResults('scmUpdateStdout');
|
||||
break;
|
||||
case 'inventory_update':
|
||||
goTojobResults('inventorySyncStdout');
|
||||
break;
|
||||
case 'workflow_job':
|
||||
goTojobResults('workflowResults');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$on('ws-jobs', () => {
|
||||
new InstanceGroup(['get', 'options'], [instance_group_id, instance_group_id])
|
||||
.then((instance_group) => {
|
||||
return instance_group.extend('get', 'jobs', {params: {page_size: "10", order_by: "-finished"}});
|
||||
})
|
||||
.then((instance_group) => {
|
||||
instanceGroup = instance_group;
|
||||
init();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
InstanceGroupJobsController.$inject = [
|
||||
'$scope',
|
||||
'$filter',
|
||||
'$state',
|
||||
'resolvedModels',
|
||||
'InstanceGroupsStrings',
|
||||
'JobStrings',
|
||||
'InstanceGroupModel'
|
||||
];
|
||||
|
||||
export default InstanceGroupJobsController;
|
||||
30
awx/ui/client/src/instance-groups/jobs/jobs.strings.js
Normal file
30
awx/ui/client/src/instance-groups/jobs/jobs.strings.js
Normal file
@@ -0,0 +1,30 @@
|
||||
function JobStrings (BaseString) {
|
||||
BaseString.call(this, 'jobs');
|
||||
|
||||
const { t } = this;
|
||||
const ns = this.jobs;
|
||||
|
||||
ns.state = {
|
||||
LIST_BREADCRUMB_LABEL: t.s('JOBS')
|
||||
};
|
||||
|
||||
ns.list = {
|
||||
PANEL_TITLE: t.s('JOBS'),
|
||||
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_TEMPLATE: t.s('Template'),
|
||||
ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'),
|
||||
ROW_ITEM_LABEL_MODIFIED: t.s('Last Modified'),
|
||||
ROW_ITEM_LABEL_RAN: t.s('Last Ran'),
|
||||
ROW_ITEM_LABEL_STARTED: t.s('Started'),
|
||||
ROW_ITEM_LABEL_FINISHED: t.s('Finished')
|
||||
};
|
||||
}
|
||||
|
||||
JobStrings.$inject = ['BaseStringService'];
|
||||
|
||||
export default JobStrings;
|
||||
@@ -1,19 +1,66 @@
|
||||
export default ['$scope', 'InstanceGroupList', 'GetBasePath', 'Rest', 'Dataset','Find', '$state',
|
||||
function($scope, InstanceGroupList, GetBasePath, Rest, Dataset, Find, $state) {
|
||||
let list = InstanceGroupList;
|
||||
export default ['$scope', 'resolvedModels', 'Dataset', '$state', 'ComponentsStrings', 'ProcessErrors', 'Wait',
|
||||
function($scope, resolvedModels, Dataset, $state, strings, ProcessErrors, Wait) {
|
||||
const vm = this;
|
||||
const { instanceGroup } = resolvedModels;
|
||||
|
||||
vm.strings = strings;
|
||||
$scope.selection = {};
|
||||
|
||||
init();
|
||||
|
||||
function init(){
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
$scope.list = {
|
||||
iterator: 'instance_group',
|
||||
name: 'instance_groups'
|
||||
};
|
||||
|
||||
$scope.collection = {
|
||||
basePath: 'instance_groups',
|
||||
iterator: 'instance_group'
|
||||
};
|
||||
|
||||
$scope[`${$scope.list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[$scope.list.name] = $scope[`${$scope.list.iterator}_dataset`].results;
|
||||
$scope.instanceGroupCount = Dataset.data.count;
|
||||
|
||||
$scope.$on('updateDataset', function(e, dataset) {
|
||||
$scope[`${$scope.list.iterator}_dataset`] = dataset;
|
||||
$scope[$scope.list.name] = dataset.results;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.isActive = function(id) {
|
||||
let selected = parseInt($state.params.instance_group_id);
|
||||
return id === selected;
|
||||
$scope.$watch('$state.params.instance_group_id', () => {
|
||||
vm.activeId = parseInt($state.params.instance_group_id);
|
||||
});
|
||||
|
||||
vm.delete = () => {
|
||||
Wait('start');
|
||||
let deletables = $scope.selection;
|
||||
deletables = Object.keys(deletables).filter((n) => deletables[n]);
|
||||
|
||||
deletables.forEach((data) => {
|
||||
let promise = instanceGroup.http.delete({resource: data});
|
||||
Promise.resolve(promise).then(vm.onSaveSuccess)
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: ' + status
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
Wait('stop');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
vm.onSaveSuccess = () => {
|
||||
$state.transitionTo($state.current, $state.params, {
|
||||
reload: true, location: true, inherit: false, notify: true
|
||||
});
|
||||
};
|
||||
|
||||
$scope.createInstanceGroup = () => {
|
||||
$state.go('instanceGroups.add');
|
||||
};
|
||||
}
|
||||
];
|
||||
];
|
||||
|
||||
@@ -1,63 +1,82 @@
|
||||
<div class="List-header">
|
||||
<div class="List-title">
|
||||
<div class="List-titleText" translate>
|
||||
INSTANCE GROUPS
|
||||
</div>
|
||||
<at-panel>
|
||||
<at-panel-heading hide-dismiss="true">
|
||||
{{ vm.strings.get('layout.INSTANCE_GROUPS') }}
|
||||
<span class="badge List-titleBadge">
|
||||
{{ instanceGroupCount }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</at-panel-heading>
|
||||
|
||||
<smart-search django-model="instance_groups" base-path="instance_groups" iterator="instance_group" list="list" dataset="instance_group_dataset"
|
||||
collection="instance_groups" search-tags="searchTags">
|
||||
</smart-search>
|
||||
<at-panel-body>
|
||||
<div class="at-List-toolbar">
|
||||
<smart-search
|
||||
class="at-List-search"
|
||||
django-model="instance_groups"
|
||||
base-path="instance_groups"
|
||||
iterator="instance_group"
|
||||
list="list"
|
||||
dataset="instance_group_dataset"
|
||||
collection="collection"
|
||||
search-tags="searchTags">
|
||||
</smart-search>
|
||||
<div class="at-List-toolbarAction">
|
||||
<div ng-click="vm.delete()"
|
||||
class="at-RowAction at-RowAction--danger">
|
||||
<i class="fa fa-trash"></i>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
ui-sref="instanceGroups.add"
|
||||
class="at-Button--add"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="List-noItems" ng-show="instance_groups.length < 1" translate>PLEASE ADD ITEMS TO THIS LIST</div>
|
||||
<at-list>
|
||||
<at-row ng-repeat="instance_group in instance_groups"
|
||||
ng-class="{'at-Row--active': (instance_group.id === vm.activeId)}">
|
||||
|
||||
<div class="list-table-container" ng-show="instance_groups.length > 0">
|
||||
<table id="instance_groups_table" class="List-table" is-extended="false">
|
||||
<thead>
|
||||
<tr class="List-tableHeaderRow">
|
||||
<th id="instance_group-name-header" class="List-tableHeader list-header col-md-5 col-sm-5 col-xs-5" ng-click="columnNoSort !== 'true' && toggleColumnOrderBy()"
|
||||
ng-class="{'list-header-noSort' : columnNoSort === 'true'}" base-path="instance_groups" collection="instance_groups"
|
||||
dataset="instance_group_dataset" column-sort="" column-field="name" column-iterator="instance_group" column-no-sort="undefined"
|
||||
column-label="Name" column-custom-class="col-md-5 col-sm-5 col-xs-5" query-set="instance_group_queryset">
|
||||
"{{'Name' | translate}}"
|
||||
<i ng-if="columnNoSort !== 'true'" class="fa columnSortIcon fa-sort-up" ng-class="orderByIcon()"></i>
|
||||
</th>
|
||||
<th id="instance_group-jobs_running-header" class="List-tableHeader list-header list-header-noSort" translate>
|
||||
Running Jobs
|
||||
</th>
|
||||
<th id="instance_group-consumed_capacity-header" class="List-tableHeader list-header list-header-noSort" translate>
|
||||
Used Capacity
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ngRepeat: instance_group in instance_groups -->
|
||||
<tr ng-class="{isActive: isActive(instance_group.id)}" id="instance_group.id" class="List-tableRow instance_group_class ng-scope" ng-repeat="instance_group in instance_groups">
|
||||
<td class="List-tableCell name-column col-md-5 col-sm-5 col-xs-5">
|
||||
<a ui-sref="instanceGroups.instances.list({instance_group_id: instance_group.id})" class="ng-binding" >{{ instance_group.name }}</a>
|
||||
<span class="badge List-titleBadge">{{ instance_group.instances }}</span>
|
||||
</td>
|
||||
<td class="List-tableCell jobs_running-column ng-binding">
|
||||
<a ui-sref="instanceGroups.instances.jobs({instance_group_id: instance_group.id})">
|
||||
{{ instance_group.jobs_running }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="List-tableCell List-tableCell--capacityColumn ng-binding">
|
||||
<capacity-bar capacity="instance_group.consumed_capacity" total-capacity="instance_group.capacity"></capacity-bar>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<input type="checkbox"
|
||||
class="at-Row-checkbox"
|
||||
ng-model="selection[instance_group.id]"/>
|
||||
|
||||
<div class="at-Row-items">
|
||||
<at-row-item
|
||||
header-value="{{ instance_group.name }}"
|
||||
header-link="/#/instance_groups/{{ instance_group.id }}">
|
||||
</at-row-item>
|
||||
|
||||
<div class="at-Row--rowLayout">
|
||||
<at-row-item
|
||||
label-value="Instances"
|
||||
label-link="/#/instance_groups/{{ instance_group.id }}/instances"
|
||||
value="{{ instance_group.instances }}"
|
||||
badge="true">
|
||||
</at-row-item>
|
||||
|
||||
<at-row-item
|
||||
label-value="Running Jobs"
|
||||
label-link="/#/instance_groups/{{ instance_group.id }}/jobs"
|
||||
value="{{ instance_group.jobs_running }}"
|
||||
badge="true">
|
||||
</at-row-item>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="at-Row-actions">
|
||||
<capacity-bar label-value="Used Capacity" capacity="instance_group.consumed_capacity" total-capacity="instance_group.capacity"></capacity-bar>
|
||||
</div>
|
||||
</at-row>
|
||||
</at-list>
|
||||
</at-panel-body>
|
||||
<paginate
|
||||
base-path="instance_groups"
|
||||
iterator="instance_group"
|
||||
dataset="instance_group_dataset"
|
||||
collection="instance_groups"
|
||||
query-set="instance_group_queryset">
|
||||
</paginate>
|
||||
</at-panel>
|
||||
|
||||
<paginate
|
||||
base-path="instance_groups"
|
||||
iterator="instance_group"
|
||||
dataset="instance_group_dataset"
|
||||
collection="instance_groups"
|
||||
query-set="instance_group_queryset">
|
||||
</paginate>
|
||||
|
||||
@@ -1,58 +1,349 @@
|
||||
import InstanceGroupsList from './list/instance-groups-list.controller';
|
||||
import { templateUrl } from '../shared/template-url/template-url.factory';
|
||||
import CapacityAdjuster from './capacity-adjuster/capacity-adjuster.directive';
|
||||
import CapacityBar from './capacity-bar/capacity-bar.directive';
|
||||
import instanceGroupsMultiselect from '../shared/instance-groups-multiselect/instance-groups.directive';
|
||||
import instanceGroupsModal from '../shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.directive';
|
||||
import instanceGroupsRoute from './instance-groups.route';
|
||||
import instancesListRoute from './instances/instances-list.route';
|
||||
import JobsList from './jobs/jobs.list';
|
||||
import jobsListRoute from './jobs/jobs-list.route';
|
||||
import JobsListController from './jobs/jobs.controller';
|
||||
import InstanceList from './instances/instances.list';
|
||||
import instancesRoute from './instances/instances.route';
|
||||
|
||||
import AddEditTemplate from './add-edit/add-edit-instance-groups.view.html';
|
||||
import AddInstanceGroupController from './add-edit/add-instance-group.controller';
|
||||
import EditInstanceGroupController from './add-edit/edit-instance-group.controller';
|
||||
import InstanceListPolicy from './add-edit/instance-list-policy.directive.js';
|
||||
|
||||
import InstanceGroupsTemplate from './list/instance-groups-list.partial.html';
|
||||
import InstanceGroupsListController from './list/instance-groups-list.controller';
|
||||
|
||||
import InstancesTemplate from './instances/instances-list.partial.html';
|
||||
import InstanceListController from './instances/instances.controller';
|
||||
import InstanceJobsList from './instances/instance-jobs/instance-jobs.list';
|
||||
import instanceJobsRoute from './instances/instance-jobs/instance-jobs.route';
|
||||
import instanceJobsListRoute from './instances/instance-jobs/instance-jobs-list.route';
|
||||
import InstanceJobsController from './instances/instance-jobs/instance-jobs.controller';
|
||||
import CapacityBar from './capacity-bar/main';
|
||||
|
||||
import JobsTemplate from './jobs/jobs-list.partial.html';
|
||||
import InstanceGroupJobsListController from './jobs/jobs.controller';
|
||||
import InstanceJobsListController from './instances/instance-jobs/instance-jobs.controller';
|
||||
|
||||
import InstanceModalTemplate from './instances/instance-modal.partial.html';
|
||||
import InstanceModalController from './instances/instance-modal.controller.js';
|
||||
|
||||
import list from './instance-groups.list';
|
||||
import service from './instance-groups.service';
|
||||
|
||||
export default
|
||||
angular.module('instanceGroups', [CapacityBar.name])
|
||||
import InstanceGroupsStrings from './instance-groups.strings';
|
||||
import JobStrings from './jobs/jobs.strings';
|
||||
|
||||
const MODULE_NAME = 'instanceGroups';
|
||||
|
||||
function InstanceGroupsResolve ($q, $stateParams, InstanceGroup, Instance) {
|
||||
const instanceGroupId = $stateParams.instance_group_id;
|
||||
const instanceId = $stateParams.instance_id;
|
||||
let promises = {};
|
||||
|
||||
if (!instanceGroupId && !instanceId) {
|
||||
promises.instanceGroup = new InstanceGroup(['get', 'options']);
|
||||
promises.instance = new Instance(['get', 'options']);
|
||||
|
||||
return $q.all(promises);
|
||||
}
|
||||
|
||||
if (instanceGroupId && instanceId) {
|
||||
promises.instance = new Instance(['get', 'options'], [instanceId, instanceId])
|
||||
.then((instance) => instance.extend('get', 'jobs', {params: {page_size: "10", order_by: "-finished"}}));
|
||||
|
||||
return $q.all(promises);
|
||||
}
|
||||
|
||||
promises.instanceGroup = new InstanceGroup(['get', 'options'], [instanceGroupId, instanceGroupId])
|
||||
.then((instanceGroup) => instanceGroup.extend('get', 'jobs', {params: {page_size: "10", order_by: "-finished"}}))
|
||||
.then((instanceGroup) => instanceGroup.extend('get', 'instances'));
|
||||
promises.instance = new Instance('get');
|
||||
|
||||
|
||||
return $q.all(promises)
|
||||
.then(models => models);
|
||||
}
|
||||
|
||||
InstanceGroupsResolve.$inject = [
|
||||
'$q',
|
||||
'$stateParams',
|
||||
'InstanceGroupModel',
|
||||
'InstanceModel'
|
||||
];
|
||||
|
||||
function InstanceGroupsRun ($stateExtender, strings, ComponentsStrings) {
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups',
|
||||
url: '/instance_groups',
|
||||
searchPrefix: 'instance_group',
|
||||
ncyBreadcrumb: {
|
||||
label: ComponentsStrings.get('layout.INSTANCE_GROUPS')
|
||||
},
|
||||
params: {
|
||||
instance_group_search: {
|
||||
value: {
|
||||
page_size: '10',
|
||||
order_by: 'name'
|
||||
},
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
data: {
|
||||
alwaysShowRefreshButton: true,
|
||||
},
|
||||
views: {
|
||||
'@': {
|
||||
templateUrl: templateUrl('./instance-groups/instance-groups'),
|
||||
},
|
||||
'list@instanceGroups': {
|
||||
templateUrl: InstanceGroupsTemplate,
|
||||
controller: 'InstanceGroupsListController',
|
||||
controllerAs: 'vm'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
resolvedModels: InstanceGroupsResolve,
|
||||
Dataset: ['InstanceGroupList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.add',
|
||||
url: '/add',
|
||||
ncyBreadcrumb: {
|
||||
label: strings.get('state.ADD_BREADCRUMB_LABEL')
|
||||
},
|
||||
views: {
|
||||
'add@instanceGroups': {
|
||||
templateUrl: AddEditTemplate,
|
||||
controller: AddInstanceGroupController,
|
||||
controllerAs: 'vm'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
}
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.add.modal',
|
||||
abstract: true,
|
||||
ncyBreadcrumb: {
|
||||
skip: true,
|
||||
},
|
||||
views: {
|
||||
"modal": {
|
||||
template: `<div class="Modal-backdrop"></div>
|
||||
<div class="Modal-holder" ui-view="modal" autoscroll="false"></div>`,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.add.modal.instances',
|
||||
ncyBreadcrumb: {
|
||||
skip: true,
|
||||
},
|
||||
views: {
|
||||
"modal": {
|
||||
template: '<instance-list-policy></instance-list-policy>',
|
||||
}
|
||||
},
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.edit',
|
||||
route: '/:instance_group_id',
|
||||
ncyBreadcrumb: {
|
||||
label: strings.get('state.EDIT_BREADCRUMB_LABEL')
|
||||
},
|
||||
views: {
|
||||
'edit@instanceGroups': {
|
||||
templateUrl: AddEditTemplate,
|
||||
controller: EditInstanceGroupController,
|
||||
controllerAs: 'vm'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.edit.modal',
|
||||
abstract: true,
|
||||
ncyBreadcrumb: {
|
||||
skip: true,
|
||||
},
|
||||
views: {
|
||||
"modal": {
|
||||
template: `<div class="Modal-backdrop"></div>
|
||||
<div class="Modal-holder" ui-view="modal" autoscroll="false"></div>`,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.edit.modal.instances',
|
||||
ncyBreadcrumb: {
|
||||
skip: true,
|
||||
},
|
||||
views: {
|
||||
"modal": {
|
||||
template: '<instance-list-policy></instance-list-policy>',
|
||||
}
|
||||
},
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.instances',
|
||||
url: '/:instance_group_id/instances',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'instanceGroups.edit',
|
||||
label: ComponentsStrings.get('layout.INSTANCES')
|
||||
},
|
||||
params: {
|
||||
instance_search: {
|
||||
value: {
|
||||
page_size: '10',
|
||||
order_by: 'hostname'
|
||||
},
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
views: {
|
||||
'instances@instanceGroups': {
|
||||
templateUrl: InstancesTemplate,
|
||||
controller: 'InstanceListController',
|
||||
controllerAs: 'vm'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
}
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.instances.modal',
|
||||
abstract: true,
|
||||
ncyBreadcrumb: {
|
||||
skip: true,
|
||||
},
|
||||
views: {
|
||||
"modal": {
|
||||
template: `<div class="Modal-backdrop"></div>
|
||||
<div class="Modal-holder" ui-view="modal" autoscroll="false"></div>`,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.instances.modal.add',
|
||||
ncyBreadcrumb: {
|
||||
skip: true,
|
||||
},
|
||||
views: {
|
||||
"modal": {
|
||||
templateUrl: InstanceModalTemplate,
|
||||
controller: InstanceModalController,
|
||||
controllerAs: 'vm'
|
||||
}
|
||||
},
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.instanceJobs',
|
||||
url: '/:instance_group_id/instances/:instance_id/jobs',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'instanceGroups.instances',
|
||||
label: ComponentsStrings.get('layout.JOBS')
|
||||
},
|
||||
views: {
|
||||
'instanceJobs@instanceGroups': {
|
||||
templateUrl: JobsTemplate,
|
||||
controller: 'InstanceJobsListController',
|
||||
controllerAs: 'vm'
|
||||
},
|
||||
},
|
||||
params: {
|
||||
job_search: {
|
||||
value: {
|
||||
page_size: '10',
|
||||
order_by: '-finished'
|
||||
},
|
||||
dynamic: true
|
||||
},
|
||||
},
|
||||
data: {
|
||||
socket: {
|
||||
"groups": {
|
||||
"jobs": ["status_changed"],
|
||||
}
|
||||
}
|
||||
},
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
});
|
||||
|
||||
$stateExtender.addState({
|
||||
name: 'instanceGroups.jobs',
|
||||
url: '/:instance_group_id/jobs',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'instanceGroups.edit',
|
||||
label: ComponentsStrings.get('layout.JOBS')
|
||||
},
|
||||
params: {
|
||||
job_search: {
|
||||
value: {
|
||||
page_size: '10',
|
||||
order_by: '-finished'
|
||||
},
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
data: {
|
||||
socket: {
|
||||
"groups": {
|
||||
"jobs": ["status_changed"],
|
||||
}
|
||||
}
|
||||
},
|
||||
views: {
|
||||
'jobs@instanceGroups': {
|
||||
templateUrl: JobsTemplate,
|
||||
controller: 'InstanceGroupJobsListController',
|
||||
controllerAs: 'vm'
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
resolvedModels: InstanceGroupsResolve
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
InstanceGroupsRun.$inject = [
|
||||
'$stateExtender',
|
||||
'InstanceGroupsStrings',
|
||||
'ComponentsStrings'
|
||||
];
|
||||
|
||||
angular.module(MODULE_NAME, [])
|
||||
.service('InstanceGroupsService', service)
|
||||
.factory('InstanceGroupList', list)
|
||||
.factory('JobsList', JobsList)
|
||||
.factory('InstanceList', InstanceList)
|
||||
.factory('InstanceJobsList', InstanceJobsList)
|
||||
.controller('InstanceGroupsList', InstanceGroupsList)
|
||||
.controller('JobsListController', JobsListController)
|
||||
.controller('InstanceGroupsListController', InstanceGroupsListController)
|
||||
.controller('InstanceGroupJobsListController', InstanceGroupJobsListController)
|
||||
.controller('InstanceListController', InstanceListController)
|
||||
.controller('InstanceJobsController', InstanceJobsController)
|
||||
.controller('InstanceJobsListController', InstanceJobsListController)
|
||||
.directive('instanceListPolicy', InstanceListPolicy)
|
||||
.directive('instanceGroupsMultiselect', instanceGroupsMultiselect)
|
||||
.directive('instanceGroupsModal', instanceGroupsModal)
|
||||
.config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider',
|
||||
function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) {
|
||||
let stateExtender = $stateExtenderProvider.$get();
|
||||
.directive('capacityAdjuster', CapacityAdjuster)
|
||||
.directive('capacityBar', CapacityBar)
|
||||
.service('InstanceGroupsStrings', InstanceGroupsStrings)
|
||||
.service('JobStrings', JobStrings)
|
||||
.run(InstanceGroupsRun);
|
||||
|
||||
|
||||
function generateInstanceGroupsStates() {
|
||||
return new Promise((resolve) => {
|
||||
resolve({
|
||||
states: [
|
||||
stateExtender.buildDefinition(instanceGroupsRoute),
|
||||
stateExtender.buildDefinition(instancesRoute),
|
||||
stateExtender.buildDefinition(instancesListRoute),
|
||||
stateExtender.buildDefinition(jobsListRoute),
|
||||
stateExtender.buildDefinition(instanceJobsRoute),
|
||||
stateExtender.buildDefinition(instanceJobsListRoute)
|
||||
]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$stateProvider.state({
|
||||
name: 'instanceGroups.**',
|
||||
url: '/instance_groups',
|
||||
lazyLoad: () => generateInstanceGroupsStates()
|
||||
});
|
||||
}]);
|
||||
export default MODULE_NAME;
|
||||
|
||||
@@ -38,7 +38,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities'])
|
||||
};
|
||||
})
|
||||
|
||||
// caplitalize Add to any input field where the first letter of each
|
||||
// capitalize Add to any input field where the first letter of each
|
||||
// word should be capitalized. Use in place of css test-transform.
|
||||
// For some reason "text-transform: capitalize" in breadcrumbs
|
||||
// causes a break at each blank space. And of course,
|
||||
@@ -65,6 +65,26 @@ angular.module('AWDirectives', ['RestServices', 'Utilities'])
|
||||
};
|
||||
})
|
||||
|
||||
// stringToNumber
|
||||
//
|
||||
// If your model does not contain actual numbers then this directive
|
||||
// will do the conversion in the ngModel $formatters and $parsers pipeline.
|
||||
//
|
||||
.directive('stringToNumber', function() {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
ngModel.$parsers.push(function(value) {
|
||||
return '' + value;
|
||||
});
|
||||
ngModel.$formatters.push(function(value) {
|
||||
return parseFloat(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// imageUpload
|
||||
//
|
||||
// Accepts image and returns base64 information with basic validation
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</div>
|
||||
<div class="MultiSelectPreview-previewTag MultiSelectPreview-previewTag--deletable">
|
||||
<span>{{selectedRow.name}}</span>
|
||||
<span ng-if="selectedRow.hostname">{{selectedRow.hostname}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
}
|
||||
|
||||
.SmartStatus-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
}
|
||||
|
||||
.SmartStatus-iconDirectionPlaceholder {
|
||||
width: 16px;
|
||||
height: 8px;
|
||||
width: 14px;
|
||||
height: 7px;
|
||||
border: 1px solid #d7d7d7;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
@@ -32,8 +32,8 @@
|
||||
}
|
||||
|
||||
.SmartStatus-iconIndicator {
|
||||
width: 16px;
|
||||
height: 8px;
|
||||
width: 14px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
.SmartStatus-iconIndicator--success {
|
||||
@@ -45,8 +45,8 @@
|
||||
}
|
||||
|
||||
.SmartStatus-iconPlaceholder {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
border: 1px solid #d7d7d7;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
@@ -132,6 +132,9 @@ export default ['$scope', '$rootScope', '$stateParams', 'UserForm', 'Rest',
|
||||
case 'organization':
|
||||
$state.go('organizations.edit', { "organization_id": id }, { reload: true });
|
||||
break;
|
||||
case 'team':
|
||||
$state.go('teams.edit', { "team_id": id }, { reload: true });
|
||||
break;
|
||||
case 'credential':
|
||||
$state.go('credentials.edit', { "credential_id": id }, { reload: true });
|
||||
break;
|
||||
|
||||
@@ -57,8 +57,8 @@ register(
|
||||
'MAX_UI_JOB_EVENTS',
|
||||
field_class=fields.IntegerField,
|
||||
min_value=100,
|
||||
label=_('Max Job Events Retreived by UI'),
|
||||
help_text=_('Maximum number of job events for the UI to retreive within a '
|
||||
label=_('Max Job Events Retrieved by UI'),
|
||||
help_text=_('Maximum number of job events for the UI to retrieve within a '
|
||||
'single request.'),
|
||||
category=_('UI'),
|
||||
category_slug='ui',
|
||||
|
||||
@@ -61,8 +61,21 @@ const getInventory = (namespace = session) => getOrganization(namespace)
|
||||
.then(organization => getOrCreate('/inventories/', {
|
||||
name: `${namespace}-inventory`,
|
||||
description: namespace,
|
||||
organization: organization.id
|
||||
}));
|
||||
organization: organization.id,
|
||||
}).then(inventory => getOrCreate('/hosts/', {
|
||||
name: `${namespace}-host`,
|
||||
description: namespace,
|
||||
inventory: inventory.id,
|
||||
variables: JSON.stringify({ ansible_connection: 'local' }),
|
||||
}).then(() => inventory)));
|
||||
|
||||
const getHost = (namespace = session) => getInventory(namespace)
|
||||
.then(inventory => getOrCreate('/hosts/', {
|
||||
name: `${namespace}-host`,
|
||||
description: namespace,
|
||||
inventory: inventory.id,
|
||||
variables: JSON.stringify({ ansible_connection: 'local' }),
|
||||
}).then((host) => host));
|
||||
|
||||
const getInventoryScript = (namespace = session) => getOrganization(namespace)
|
||||
.then(organization => getOrCreate('/inventory_scripts/', {
|
||||
@@ -182,7 +195,7 @@ const waitForJob = endpoint => {
|
||||
const completed = statuses.indexOf(update.data.status) > -1;
|
||||
|
||||
if (completed) {
|
||||
return resolve();
|
||||
return resolve(update.data);
|
||||
}
|
||||
|
||||
if (--attempts <= 0) {
|
||||
@@ -206,6 +219,15 @@ const getUpdatedProject = (namespace = session) => getProject(namespace)
|
||||
return project;
|
||||
});
|
||||
|
||||
const getJob = (namespace = session) => getJobTemplate(namespace)
|
||||
.then(template => {
|
||||
const launchURL = template.related.launch;
|
||||
return post(launchURL, {}).then(response => {
|
||||
const jobURL = response.data.url;
|
||||
return waitForJob(jobURL).then(() => response.data);
|
||||
});
|
||||
});
|
||||
|
||||
const getJobTemplate = (namespace = session) => {
|
||||
const promises = [
|
||||
getInventory(namespace),
|
||||
@@ -271,6 +293,29 @@ const getJobTemplateAdmin = (namespace = session) => {
|
||||
.then(spread(user => user));
|
||||
};
|
||||
|
||||
const getProjectAdmin = (namespace = session) => {
|
||||
const rolePromise = getUpdatedProject(namespace)
|
||||
.then(obj => obj.summary_fields.object_roles.admin_role);
|
||||
|
||||
const userPromise = getOrganization(namespace)
|
||||
.then(obj => getOrCreate('/users/', {
|
||||
username: `project-admin-${uuid().substr(0, 8)}`,
|
||||
organization: obj.id,
|
||||
first_name: 'firstname',
|
||||
last_name: 'lastname',
|
||||
email: 'null@ansible.com',
|
||||
is_superuser: false,
|
||||
is_system_auditor: false,
|
||||
password: AWX_E2E_PASSWORD
|
||||
}));
|
||||
|
||||
const assignRolePromise = Promise.all([userPromise, rolePromise])
|
||||
.then(spread((user, role) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id })));
|
||||
|
||||
return Promise.all([userPromise, assignRolePromise])
|
||||
.then(spread(user => user));
|
||||
};
|
||||
|
||||
const getInventorySourceSchedule = (namespace = session) => getInventorySource(namespace)
|
||||
.then(source => getOrCreate(source.related.schedules, {
|
||||
name: `${source.name}-schedule`,
|
||||
@@ -282,7 +327,7 @@ const getJobTemplateSchedule = (namespace = session) => getJobTemplate(namespace
|
||||
.then(template => getOrCreate(template.related.schedules, {
|
||||
name: `${template.name}-schedule`,
|
||||
description: namespace,
|
||||
rrule: 'DTSTART:20171104T040000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1'
|
||||
rrule: 'DTSTART:20351104T040000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1'
|
||||
}));
|
||||
|
||||
module.exports = {
|
||||
@@ -299,8 +344,11 @@ module.exports = {
|
||||
getNotificationTemplate,
|
||||
getOrCreate,
|
||||
getOrganization,
|
||||
getProjectAdmin,
|
||||
getSmartInventory,
|
||||
getTeam,
|
||||
getUpdatedProject,
|
||||
getUser
|
||||
getUser,
|
||||
getJob,
|
||||
getHost,
|
||||
};
|
||||
|
||||
10
awx/ui/test/e2e/objects/jobs.js
Normal file
10
awx/ui/test/e2e/objects/jobs.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
module.exports = {
|
||||
url () {
|
||||
return `${this.api.globals.launch_url}/#/jobs`;
|
||||
},
|
||||
sections: {}, // TODO: Fill this out
|
||||
elements: {}, // TODO: Fill this out
|
||||
commands: [], // TODO: Fill this out as needed
|
||||
};
|
||||
@@ -1,17 +1,19 @@
|
||||
import {
|
||||
getAdminMachineCredential,
|
||||
getHost,
|
||||
getInventory,
|
||||
getInventoryScript,
|
||||
getInventorySource,
|
||||
getInventorySourceSchedule,
|
||||
getJobTemplate,
|
||||
getJobTemplateAdmin,
|
||||
getJobTemplateSchedule,
|
||||
getNotificationTemplate,
|
||||
getOrganization,
|
||||
getProjectAdmin,
|
||||
getSmartInventory,
|
||||
getTeam,
|
||||
getUpdatedProject,
|
||||
getJob,
|
||||
} from '../fixtures';
|
||||
|
||||
const data = {};
|
||||
@@ -21,9 +23,11 @@ const pages = {};
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
const namespace = '<div id="xss" class="xss">test</div>';
|
||||
const namespaceShort = '<div class="xss">t</div>';
|
||||
|
||||
const resources = [
|
||||
getOrganization(namespace).then(obj => { data.organization = obj; }),
|
||||
getHost(namespaceShort).then(obj => { data.host = obj; }),
|
||||
getInventory(namespace).then(obj => { data.inventory = obj; }),
|
||||
getInventoryScript(namespace).then(obj => { data.inventoryScript = obj; }),
|
||||
getSmartInventory(namespace).then(obj => { data.smartInventory = obj; }),
|
||||
@@ -34,8 +38,9 @@ module.exports = {
|
||||
getJobTemplate(namespace).then(obj => { data.jobTemplate = obj; }),
|
||||
getJobTemplateSchedule(namespace).then(obj => { data.jobTemplateSchedule = obj; }),
|
||||
getTeam(namespace).then(obj => { data.team = obj; }),
|
||||
getJobTemplateAdmin(namespace).then(obj => { data.user = obj; }),
|
||||
getProjectAdmin(namespace).then(obj => { data.user = obj; }),
|
||||
getNotificationTemplate(namespace).then(obj => { data.notification = obj; }),
|
||||
getJob(namespaceShort).then(obj => { data.job = obj; }),
|
||||
];
|
||||
|
||||
Promise.all(resources)
|
||||
@@ -49,9 +54,11 @@ module.exports = {
|
||||
pages.teams = client.page.teams();
|
||||
pages.users = client.page.users();
|
||||
pages.notificationTemplates = client.page.notificationTemplates();
|
||||
pages.jobs = client.page.jobs();
|
||||
|
||||
urls.organization = `${pages.organizations.url()}/${data.organization.id}`;
|
||||
urls.inventory = `${pages.inventories.url()}/inventory/${data.inventory.id}`;
|
||||
urls.inventoryHosts = `${urls.inventory}/hosts`;
|
||||
urls.inventoryScript = `${pages.inventoryScripts.url()}/${data.inventoryScript.id}`;
|
||||
urls.inventorySource = `${urls.inventory}/inventory_sources/edit/${data.inventorySource.id}`;
|
||||
urls.sourceSchedule = `${urls.inventorySource}/schedules/${data.sourceSchedule.id}`;
|
||||
@@ -63,6 +70,8 @@ module.exports = {
|
||||
urls.team = `${pages.teams.url()}/${data.team.id}`;
|
||||
urls.user = `${pages.users.url()}/${data.user.id}`;
|
||||
urls.notification = `${pages.notificationTemplates.url()}/${data.notification.id}`;
|
||||
urls.jobs = `${pages.jobs.url()}`;
|
||||
urls.jobsSchedules = `${pages.jobs.url()}/schedules`;
|
||||
|
||||
client.useCss();
|
||||
client.login();
|
||||
@@ -97,75 +106,6 @@ module.exports = {
|
||||
client.pause(500).expect.element('div.spinny').not.visible;
|
||||
client.expect.element('#multi-credential-modal').not.present;
|
||||
},
|
||||
'check template roles list for unsanitized content': client => {
|
||||
const itemDelete = `#permissions_table tr[id="${data.user.id}"] div[class*="RoleList-deleteContainer"]`;
|
||||
|
||||
client.expect.element('#permissions_tab').visible;
|
||||
client.expect.element('#permissions_tab').enabled;
|
||||
|
||||
client.click('#permissions_tab');
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
|
||||
client.expect.element('div[ui-view="related"]').visible;
|
||||
client.expect.element('div[ui-view="related"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', `id:${data.user.id}`);
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
client.expect.element(itemDelete).visible;
|
||||
client.expect.element(itemDelete).enabled;
|
||||
|
||||
client.click(itemDelete);
|
||||
|
||||
client.expect.element('#prompt-header').visible;
|
||||
client.expect.element('#prompt-header').text.equal('USER ACCESS REMOVAL');
|
||||
client.expect.element('#prompt_cancel_btn').enabled;
|
||||
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
|
||||
client.click('#prompt_cancel_btn');
|
||||
|
||||
client.expect.element('#prompt-header').not.visible;
|
||||
},
|
||||
'check template permissions view for unsanitized content': client => {
|
||||
client.expect.element('button[aw-tool-tip="Add a permission"]').visible;
|
||||
client.expect.element('button[aw-tool-tip="Add a permission"]').enabled;
|
||||
|
||||
client.click('button[aw-tool-tip="Add a permission"]');
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
client.expect.element('div[class="AddPermissions-header"]').visible;
|
||||
client.expect.element('div[class="AddPermissions-header"]').attribute('innerHTML')
|
||||
.contains('<div id="xss" class="xss">test</div>');
|
||||
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
|
||||
client.expect.element('div[class="AddPermissions-dialog"] button[class*="exit"]').enabled;
|
||||
|
||||
client.click('div[class="AddPermissions-dialog"] button[class*="exit"]');
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
// client.expect.element('div.spinny').visible;
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
client.waitForAngular();
|
||||
|
||||
client.expect.element('#job_template_tab').enabled;
|
||||
|
||||
client.click('#job_template_tab');
|
||||
|
||||
client.expect.element('#job_template_form').visible;
|
||||
},
|
||||
'check template list for unsanitized content': client => {
|
||||
const itemRow = `#row-${data.jobTemplate.id}`;
|
||||
const itemName = `${itemRow} .at-RowItem-header`;
|
||||
@@ -219,7 +159,7 @@ module.exports = {
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
},
|
||||
'check user roles list for unsanitized content': client => {
|
||||
const adminRole = data.jobTemplate.summary_fields.object_roles.admin_role;
|
||||
const adminRole = data.project.summary_fields.object_roles.admin_role;
|
||||
const itemDelete = `#permissions_table tr[id="${adminRole.id}"] #delete-action`;
|
||||
|
||||
client.expect.element('#permissions_tab').visible;
|
||||
@@ -498,6 +438,75 @@ module.exports = {
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
},
|
||||
'check project roles list for unsanitized content': client => {
|
||||
const itemDelete = `#permissions_table tr[id="${data.user.id}"] div[class*="RoleList-deleteContainer"]`;
|
||||
|
||||
client.expect.element('#permissions_tab').visible;
|
||||
client.expect.element('#permissions_tab').enabled;
|
||||
|
||||
client.click('#permissions_tab');
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
|
||||
client.expect.element('div[ui-view="related"]').visible;
|
||||
client.expect.element('div[ui-view="related"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', `id:${data.user.id}`);
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
client.expect.element(itemDelete).visible;
|
||||
client.expect.element(itemDelete).enabled;
|
||||
|
||||
client.click(itemDelete);
|
||||
|
||||
client.expect.element('#prompt-header').visible;
|
||||
client.expect.element('#prompt-header').text.equal('USER ACCESS REMOVAL');
|
||||
client.expect.element('#prompt_cancel_btn').enabled;
|
||||
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
|
||||
client.click('#prompt_cancel_btn');
|
||||
|
||||
client.expect.element('#prompt-header').not.visible;
|
||||
},
|
||||
'check project permissions view for unsanitized content': client => {
|
||||
client.expect.element('button[aw-tool-tip="Add a permission"]').visible;
|
||||
client.expect.element('button[aw-tool-tip="Add a permission"]').enabled;
|
||||
|
||||
client.click('button[aw-tool-tip="Add a permission"]');
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
client.expect.element('div[class="AddPermissions-header"]').visible;
|
||||
client.expect.element('div[class="AddPermissions-header"]').attribute('innerHTML')
|
||||
.contains('<div id="xss" class="xss">test</div>');
|
||||
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
|
||||
client.expect.element('div[class="AddPermissions-dialog"] button[class*="exit"]').enabled;
|
||||
|
||||
client.click('div[class="AddPermissions-dialog"] button[class*="exit"]');
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
// client.expect.element('div.spinny').visible;
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
client.waitForAngular();
|
||||
|
||||
client.expect.element('#project_tab').enabled;
|
||||
|
||||
client.click('#project_tab');
|
||||
|
||||
client.expect.element('#project_form').visible;
|
||||
},
|
||||
'check project list for unsanitized content': client => {
|
||||
const itemRow = `#projects_table tr[id="${data.project.id}"]`;
|
||||
const itemName = `${itemRow} td[class*="name-"] a`;
|
||||
@@ -655,6 +664,40 @@ module.exports = {
|
||||
client.navigateTo(urls.jobTemplateSchedule);
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
},
|
||||
'check job schedules view for unsanitized content': client => {
|
||||
const itemRow = `#schedules_table tr[id="${data.jobTemplateSchedule.id}"]`;
|
||||
const itemName = `${itemRow} td[class*="name-"] a`;
|
||||
|
||||
client.navigateTo(urls.jobsSchedules);
|
||||
|
||||
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.end();
|
||||
},
|
||||
'check host recent jobs popup for unsanitized content': client => {
|
||||
const itemRow = `#hosts_table tr[id="${data.host.id}"]`;
|
||||
const itemName = `${itemRow} td[class*="active_failures-"] a`;
|
||||
const popOver = `${itemRow} td[class*="active_failures-"] div[class*="popover"]`;
|
||||
|
||||
client.navigateTo(urls.inventoryHosts);
|
||||
|
||||
client.click(itemName);
|
||||
client.expect.element(popOver).present;
|
||||
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
|
||||
client.end();
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user