diff --git a/awx/ui/client/lib/components/components.strings.js b/awx/ui/client/lib/components/components.strings.js index 93f5ab1416..ddc765533b 100644 --- a/awx/ui/client/lib/components/components.strings.js +++ b/awx/ui/client/lib/components/components.strings.js @@ -71,6 +71,7 @@ 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'), @@ -78,7 +79,8 @@ function ComponentsStrings (BaseString) { }; ns.capacityBar = { - IS_OFFLINE: t.s('Unavailable to run jobs.') + IS_OFFLINE: t.s('Unavailable to run jobs.'), + IS_OFFLINE_LABEL: t.s('Unavailable') }; ns.relaunch = { diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index 6f15cae762..d33e79329a 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -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) diff --git a/awx/ui/client/lib/components/input/_index.less b/awx/ui/client/lib/components/input/_index.less index b92692c34e..03589273eb 100644 --- a/awx/ui/client/lib/components/input/_index.less +++ b/awx/ui/client/lib/components/input/_index.less @@ -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,37 @@ 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 { + .at-mixin-sliderTrack(); + } + &::-webkit-slider-thumb { + .at-mixin-sliderThumb(); + } + } +} \ No newline at end of file diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 1e6cc13588..fcee7ad72c 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -118,6 +118,16 @@ function AtInputLookupController (baseInputController, $q, $state) { vm.searchAfterDebounce(); }; + + vm.removeTag = (i) => { + let list; + if (!i.id) { + list = _.remove(scope.state._value, i); + } else { + list = _.remove(scope.state._value, i.id); + } + scope.state._value = list; + }; } AtInputLookupController.$inject = [ diff --git a/awx/ui/client/lib/components/input/lookup.partial.html b/awx/ui/client/lib/components/input/lookup.partial.html index 21ebf03b5d..271c24212f 100644 --- a/awx/ui/client/lib/components/input/lookup.partial.html +++ b/awx/ui/client/lib/components/input/lookup.partial.html @@ -1,27 +1,45 @@
-
- +
+ -
- - - - +
+ + + + + + + + + + +
+
+ +
+
+ {{ tag.hostname }} + {{ tag }} +
+
+
+
+ +
- -
- -
-
+
+
\ No newline at end of file diff --git a/awx/ui/client/lib/components/input/slider.directive.js b/awx/ui/client/lib/components/input/slider.directive.js new file mode 100644 index 0000000000..a2e1b8c28e --- /dev/null +++ b/awx/ui/client/lib/components/input/slider.directive.js @@ -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; diff --git a/awx/ui/client/lib/components/input/slider.partial.html b/awx/ui/client/lib/components/input/slider.partial.html new file mode 100644 index 0000000000..e4649149f8 --- /dev/null +++ b/awx/ui/client/lib/components/input/slider.partial.html @@ -0,0 +1,13 @@ +
+
+ +
+ +

{{ state._value }}%

+
+
+
diff --git a/awx/ui/client/lib/components/list/_index.less b/awx/ui/client/lib/components/list/_index.less index cd1fa9a023..77997bbbcc 100644 --- a/awx/ui/client/lib/components/list/_index.less +++ b/awx/ui/client/lib/components/list/_index.less @@ -86,12 +86,26 @@ 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-Row-actions { display: flex; } .at-Row-items { align-self: flex-start; + flex: 1; } .at-RowItem { @@ -101,6 +115,7 @@ } .at-RowItem--isHeader { + color: @at-color-body-text; margin-bottom: @at-margin-bottom-list-header; line-height: @at-line-height-list-row-item-header; } @@ -146,8 +161,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 +213,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; diff --git a/awx/ui/client/lib/components/list/row-item.directive.js b/awx/ui/client/lib/components/list/row-item.directive.js index 972008f7a2..e07820468e 100644 --- a/awx/ui/client/lib/components/list/row-item.directive.js +++ b/awx/ui/client/lib/components/list/row-item.directive.js @@ -7,10 +7,13 @@ function atRowItem () { transclude: true, templateUrl, scope: { + badge: '@', headerValue: '@', headerLink: '@', headerTag: '@', labelValue: '@', + labelLink: '@', + labelState: '@', value: '@', valueLink: '@', smartStatus: '=?', diff --git a/awx/ui/client/lib/components/list/row-item.partial.html b/awx/ui/client/lib/components/list/row-item.partial.html index a9b81ae20c..ca58947b79 100644 --- a/awx/ui/client/lib/components/list/row-item.partial.html +++ b/awx/ui/client/lib/components/list/row-item.partial.html @@ -9,13 +9,19 @@
{{ headerTag }}
-
+
+ {{ labelValue }} +
+
{{ labelValue }}
+
+ {{ labelValue }} +
{{ value }}
-
- + \ No newline at end of file diff --git a/awx/ui/client/lib/components/tabs/tab.partial.html b/awx/ui/client/lib/components/tabs/tab.partial.html index 263a5d1d96..747e470571 100644 --- a/awx/ui/client/lib/components/tabs/tab.partial.html +++ b/awx/ui/client/lib/components/tabs/tab.partial.html @@ -1,6 +1,7 @@ diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index 8845c24f82..7fafb05c75 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -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); diff --git a/awx/ui/client/lib/models/Instance.js b/awx/ui/client/lib/models/Instance.js new file mode 100644 index 0000000000..09b7df0547 --- /dev/null +++ b/awx/ui/client/lib/models/Instance.js @@ -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; diff --git a/awx/ui/client/lib/models/InstanceGroup.js b/awx/ui/client/lib/models/InstanceGroup.js new file mode 100644 index 0000000000..cc82432c42 --- /dev/null +++ b/awx/ui/client/lib/models/InstanceGroup.js @@ -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; diff --git a/awx/ui/client/lib/models/Job.js b/awx/ui/client/lib/models/Job.js new file mode 100644 index 0000000000..9be420b2f9 --- /dev/null +++ b/awx/ui/client/lib/models/Job.js @@ -0,0 +1,21 @@ +let Base; + +function JobModel (method, resource, config) { + Base.call(this, 'jobs'); + + this.Constructor = JobModel; + + return this.create(method, resource, config); +} + +function JobModelLoader (BaseModel) { + Base = BaseModel; + + return JobModel; +} + +JobModelLoader.$inject = [ + 'BaseModel' +]; + +export default JobModelLoader; diff --git a/awx/ui/client/lib/models/index.js b/awx/ui/client/lib/models/index.js index 6dbea4c954..3c43af7915 100644 --- a/awx/ui/client/lib/models/index.js +++ b/awx/ui/client/lib/models/index.js @@ -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'; @@ -32,6 +34,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) diff --git a/awx/ui/client/lib/theme/_global.less b/awx/ui/client/lib/theme/_global.less index b1e46927ef..b62f501c33 100644 --- a/awx/ui/client/lib/theme/_global.less +++ b/awx/ui/client/lib/theme/_global.less @@ -15,7 +15,16 @@ background: @at-color-disabled; } } - + +.at-Button--add { + &:extend(.at-Button--success all); + &:before { + content: "+"; + font-size: 20px; + } + border-color: transparent; +} + .at-Button--info { .at-mixin-Button(); .at-mixin-ButtonColor('at-color-info', 'at-color-default'); @@ -26,7 +35,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 +50,5 @@ } .at-Button--expand { - width: 100%; + width: 100%; } diff --git a/awx/ui/client/lib/theme/_mixins.less b/awx/ui/client/lib/theme/_mixins.less index 6dc36a7b24..d40373c836 100644 --- a/awx/ui/client/lib/theme/_mixins.less +++ b/awx/ui/client/lib/theme/_mixins.less @@ -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; @@ -102,3 +103,21 @@ .at-mixin-FontFixedWidth () { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } + +.at-mixin-sliderTrack() { + background: @at-color-input-slider-track; + cursor: pointer; + height: 1px; + width: 100%; +} + +.at-mixin-sliderThumb() { + -webkit-appearance: none; + background: @at-color-input-slider-thumb; + border-radius: 50%; + border: none; + cursor: pointer; + height: 16px; + margin-top: -7px; + width: 16px; +} \ No newline at end of file diff --git a/awx/ui/client/lib/theme/_variables.less b/awx/ui/client/lib/theme/_variables.less index be5cde41ee..cd3a8a6675 100644 --- a/awx/ui/client/lib/theme/_variables.less +++ b/awx/ui/client/lib/theme/_variables.less @@ -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; diff --git a/awx/ui/client/lib/theme/index.less b/awx/ui/client/lib/theme/index.less index 1f0d3dd254..6b3f241ffd 100644 --- a/awx/ui/client/lib/theme/index.less +++ b/awx/ui/client/lib/theme/index.less @@ -73,6 +73,7 @@ @import '../../src/home/dashboard/dashboard.block.less'; @import '../../src/instance-groups/capacity-bar/capacity-bar.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'; diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index e1667e6fb8..069ee2dd80 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -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, @@ -316,6 +316,21 @@ angular activateTab(); }); + $transitions.onCreate({}, function(trans) { + console.log('$onCreate ' +trans.to().name); + }); + + $transitions.onBefore({}, function(trans) { + console.log('$onBefore ' +trans.to().name); + }); + $transitions.onError({}, function(trans) { + + console.log('$onError ' +trans.to().name); + }); + $transitions.onExit({}, function(trans) { + console.log('$onExit ' +trans.to().name); + }); + $transitions.onSuccess({}, function(trans) { if(trans.to() === trans.from()) { diff --git a/awx/ui/client/src/instance-groups/add-edit/add-edit-instance-groups.view.html b/awx/ui/client/src/instance-groups/add-edit/add-edit-instance-groups.view.html new file mode 100644 index 0000000000..a980d74ec8 --- /dev/null +++ b/awx/ui/client/src/instance-groups/add-edit/add-edit-instance-groups.view.html @@ -0,0 +1,34 @@ + + + {{ vm.panelTitle }} + + + + {{:: vm.strings.get('tab.DETAILS') }} + {{:: vm.strings.get('tab.INSTANCES') }} + {{:: vm.strings.get('tab.JOBS') }} + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
\ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/add-edit/add-instance-group.controller.js b/awx/ui/client/src/instance-groups/add-edit/add-instance-group.controller.js new file mode 100644 index 0000000000..9fc838115b --- /dev/null +++ b/awx/ui/client/src/instance-groups/add-edit/add-instance-group.controller.js @@ -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 = "New Instance Group"; + + vm.tab = { + details: { _active: true }, + instances: {_disabled: true }, + jobs: {_disabled: true } + }; + + vm.form = instanceGroup.createFormSchema('post'); + + 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; \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/add-edit/add-instance-list-policy.controller.js b/awx/ui/client/src/instance-groups/add-edit/add-instance-list-policy.controller.js new file mode 100644 index 0000000000..c7819ba797 --- /dev/null +++ b/awx/ui/client/src/instance-groups/add-edit/add-instance-list-policy.controller.js @@ -0,0 +1,40 @@ +function InstanceModalController ($scope, $state, $http, $q, models, strings) { + const { instance } = models; + const vm = this || {}; + + vm.setInstances = () => { + vm.instances = instance.get('results').map(instance => { + instance.isSelected = false; + return instance; + }); + } + + init(); + + function init() { + vm.strings = strings; + vm.panelTitle = strings.get('instance.PANEL_TITLE'); + vm.setInstances(); + }; + + $scope.$watch('vm.instances', function() { + vm.selectedRows = _.filter(vm.instances, 'isSelected') + vm.deselectedRows = _.filter(vm.instances, 'isSelected', false); + }, true); + + vm.submit = () => { + $scope.$parent.$parent.$parent.state.policy_instance_list._value = vm.selectedRows; + $state.go("^.^"); + }; +} + +InstanceModalController.$inject = [ + '$scope', + '$state', + '$http', + '$q', + 'resolvedModels', + 'InstanceGroupsStrings' +]; + +export default InstanceModalController; diff --git a/awx/ui/client/src/instance-groups/add-edit/add-instance-list-policy.partial.html b/awx/ui/client/src/instance-groups/add-edit/add-instance-list-policy.partial.html new file mode 100644 index 0000000000..29493add34 --- /dev/null +++ b/awx/ui/client/src/instance-groups/add-edit/add-instance-list-policy.partial.html @@ -0,0 +1,53 @@ + \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/add-edit/edit-instance-group.controller.js b/awx/ui/client/src/instance-groups/add-edit/edit-instance-group.controller.js new file mode 100644 index 0000000000..49b197c6ec --- /dev/null +++ b/awx/ui/client/src/instance-groups/add-edit/edit-instance-group.controller.js @@ -0,0 +1,55 @@ +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); + 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; \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less index 1caba245f0..5668eef8ad 100644 --- a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less +++ b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less @@ -1,21 +1,22 @@ capacity-bar { - - width: 50%; - margin-right: 25px; - min-width: 100px; - display: flex; align-items: center; + color: @at-gray-70; + 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; + } +} \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.directive.js b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.directive.js index 5ea07d2dd3..7301d6e898 100644 --- a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.directive.js +++ b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.directive.js @@ -1,44 +1,47 @@ 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 (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); + } + }; +} +]; \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.partial.html b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.partial.html index d80ff84bc0..6c1fadb823 100644 --- a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.partial.html +++ b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.partial.html @@ -1,11 +1,20 @@ + + {{labelValue}} + +
-
-
+
+
-{{ consumedCapacity }} + + + {{ consumedCapacity }} + \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/instance-groups.partial.html b/awx/ui/client/src/instance-groups/instance-groups.partial.html index baeaf59f00..217efd98d0 100644 --- a/awx/ui/client/src/instance-groups/instance-groups.partial.html +++ b/awx/ui/client/src/instance-groups/instance-groups.partial.html @@ -1,11 +1,13 @@
+
+
-
-
-
+
+ +
diff --git a/awx/ui/client/src/instance-groups/instance-groups.route.js b/awx/ui/client/src/instance-groups/instance-groups.route.js deleted file mode 100644 index c265b35cd9..0000000000 --- a/awx/ui/client/src/instance-groups/instance-groups.route.js +++ /dev/null @@ -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`]); - } - ] - } -}; diff --git a/awx/ui/client/src/instance-groups/instance-groups.strings.js b/awx/ui/client/src/instance-groups/instance-groups.strings.js new file mode 100644 index 0000000000..a351fe0ca8 --- /dev/null +++ b/awx/ui/client/src/instance-groups/instance-groups.strings.js @@ -0,0 +1,25 @@ +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') + } +} + +InstanceGroupsStrings.$inject = ['BaseStringService']; + +export default InstanceGroupsStrings; \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs-list.route.js b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs-list.route.js deleted file mode 100644 index 1d82ca854e..0000000000 --- a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs-list.route.js +++ /dev/null @@ -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`]); - } - ], - } -}; diff --git a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.controller.js b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.controller.js index a7d50764f5..5fbce21d4b 100644 --- a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.controller.js +++ b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.controller.js @@ -1,82 +1,81 @@ -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, GetBasePath, Rest, Dataset, Find, $filter, $state, $q, model, strings, jobStrings) { + const vm = this || {}; + const { instance } = model; - 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(); - } - ); } -]; \ No newline at end of file + + 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; + } + }; + +} + +InstanceJobsController.$inject = [ + '$scope', + 'GetBasePath', + 'Rest', + 'Dataset', + 'Find', + '$filter', + '$state', + '$q', + 'resolvedModels', + 'InstanceGroupsStrings', + 'JobStrings' +]; + +export default InstanceJobsController; \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.list.js b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.list.js deleted file mode 100644 index 58476f0054..0000000000 --- a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.list.js +++ /dev/null @@ -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: ` - - W - - ` - }, - 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', - }, - } - }; -}]; diff --git a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.partial.html b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.partial.html deleted file mode 100644 index 9c40fe931f..0000000000 --- a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.partial.html +++ /dev/null @@ -1,32 +0,0 @@ -
-
-
-
-
-
{{ instanceName }}
-
-
-
-

Used Capacity

- -
-
-

Running Jobs

- - {{ instanceJobsRunning }} - -
-
-
- -
-
-
-
JOBS
-
-
-
-
-
diff --git a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.route.js b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.route.js deleted file mode 100644 index 7e9be9a9de..0000000000 --- a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs.route.js +++ /dev/null @@ -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 - }); - }); - }] - } -}; diff --git a/awx/ui/client/src/instance-groups/instances/instance-modal.block.less b/awx/ui/client/src/instance-groups/instances/instance-modal.block.less new file mode 100644 index 0000000000..49ea9e10e7 --- /dev/null +++ b/awx/ui/client/src/instance-groups/instances/instance-modal.block.less @@ -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; + } +} diff --git a/awx/ui/client/src/instance-groups/instances/instance-modal.controller.js b/awx/ui/client/src/instance-groups/instances/instance-modal.controller.js new file mode 100644 index 0000000000..dcb2c436d6 --- /dev/null +++ b/awx/ui/client/src/instance-groups/instances/instance-modal.controller.js @@ -0,0 +1,74 @@ +function InstanceModalController ($scope, $state, $http, $q, models, strings) { + 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 = () => { + let associate = vm.selectedRows + .map(instance => ({id: instance.id})); + let disassociate = vm.deselectedRows + .map(instance => ({id: instance.id, disassociate: true})); + + let all = associate.concat(disassociate); + let defers = all.map((data) => { + let config = { + url: `${vm.instanceGroupId}/instances/`, + data: data + } + return instanceGroup.http.post(config); + }); + + Promise.all(defers) + .then(vm.onSaveSuccess); + }; + + vm.onSaveSuccess = () => { + $state.go('instanceGroups.instances', {}, {reload: 'instanceGroups.instances'}); + }; +} + +InstanceModalController.$inject = [ + '$scope', + '$state', + '$http', + '$q', + 'resolvedModels', + 'InstanceGroupsStrings' +]; + +export default InstanceModalController; diff --git a/awx/ui/client/src/instance-groups/instances/instance-modal.partial.html b/awx/ui/client/src/instance-groups/instances/instance-modal.partial.html new file mode 100644 index 0000000000..0eb9187064 --- /dev/null +++ b/awx/ui/client/src/instance-groups/instances/instance-modal.partial.html @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/instances/instances-list.partial.html b/awx/ui/client/src/instance-groups/instances/instances-list.partial.html index da8f052423..814588a101 100644 --- a/awx/ui/client/src/instance-groups/instances/instances-list.partial.html +++ b/awx/ui/client/src/instance-groups/instances/instances-list.partial.html @@ -1,44 +1,62 @@ -
- - + + + {{ vm.panelTitle }} + -
PLEASE ADD ITEMS TO THIS LIST
-
- - - - - - - - - - - - - - - - -
- "{{'Name' | translate}}" - - - Running Jobs - - Used Capacity -
- {{ instance.hostname }} - - - {{ instance.jobs_running }} - - - -
-
-
+ + {{:: vm.strings.get('tab.DETAILS') }} + {{:: vm.strings.get('tab.INSTANCES') }} + {{:: vm.strings.get('tab.JOBS') }} + + + +
+ + + +
+ +
+
+
+ + + +
+ + +
+ + +
+
+ +
+ +
+
+
+
+ diff --git a/awx/ui/client/src/instance-groups/instances/instances-list.route.js b/awx/ui/client/src/instance-groups/instances/instances-list.route.js deleted file mode 100644 index 16549d9d1e..0000000000 --- a/awx/ui/client/src/instance-groups/instances/instances-list.route.js +++ /dev/null @@ -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`]); - } - ] - } -}; diff --git a/awx/ui/client/src/instance-groups/instances/instances.controller.js b/awx/ui/client/src/instance-groups/instances/instances.controller.js index 0481d84263..6e8de76864 100644 --- a/awx/ui/client/src/instance-groups/instances/instances.controller.js +++ b/awx/ui/client/src/instance-groups/instances/instances.controller.js @@ -1,20 +1,55 @@ -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, models, strings, Dataset) { + 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 = { + iterator: 'instance', + name: 'instances' }; - + $scope.collection = { + basepath: 'instances', + iterator: 'instance' + }; + $scope[`${$scope.list.iterator}_dataset`] = Dataset.data; + $scope[$scope.list.name] = $scope[`${$scope.list.iterator}_dataset`].results; } -]; \ No newline at end of file + + 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 } + } + }; + + + $scope.isActive = function(id) { + let selected = parseInt($state.params.instance_id); + return id === selected; + }; +} + +InstancesController.$inject = [ + '$scope', + '$state', + 'resolvedModels', + 'InstanceGroupsStrings', + 'Dataset' +]; + +export default InstancesController; diff --git a/awx/ui/client/src/instance-groups/instances/instances.list.js b/awx/ui/client/src/instance-groups/instances/instances.list.js deleted file mode 100644 index 048279d6c8..0000000000 --- a/awx/ui/client/src/instance-groups/instances/instances.list.js +++ /dev/null @@ -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, - }, - } - }; -}]; diff --git a/awx/ui/client/src/instance-groups/instances/instances.route.js b/awx/ui/client/src/instance-groups/instances/instances.route.js deleted file mode 100644 index 8890171b58..0000000000 --- a/awx/ui/client/src/instance-groups/instances/instances.route.js +++ /dev/null @@ -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 - }); - }); - }] - } -}; diff --git a/awx/ui/client/src/instance-groups/jobs/jobs-list.route.js b/awx/ui/client/src/instance-groups/jobs/jobs-list.route.js deleted file mode 100644 index 03854eca20..0000000000 --- a/awx/ui/client/src/instance-groups/jobs/jobs-list.route.js +++ /dev/null @@ -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`]); - } - ] - } -}; diff --git a/awx/ui/client/src/instance-groups/jobs/jobs.controller.js b/awx/ui/client/src/instance-groups/jobs/jobs.controller.js index cfe2f73327..a6054805a0 100644 --- a/awx/ui/client/src/instance-groups/jobs/jobs.controller.js +++ b/awx/ui/client/src/instance-groups/jobs/jobs.controller.js @@ -1,82 +1,93 @@ -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, GetBasePath, Rest, Dataset, Find, $filter, $state, $q, model, strings, jobStrings) { + const vm = this || {}; + const { instanceGroup } = model; - 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(){ + let instance_group_id = instanceGroup.get('id'); + 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(); - } - ); } -]; \ No newline at end of file + + 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; + } + + }; +} + +InstanceGroupJobsController.$inject = [ + '$scope', + 'GetBasePath', + 'Rest', + 'Dataset', + 'Find', + '$filter', + '$state', + '$q', + 'resolvedModels', + 'InstanceGroupsStrings', + 'JobStrings' +]; + +export default InstanceGroupJobsController; \ No newline at end of file diff --git a/awx/ui/client/src/instance-groups/jobs/jobs.list.js b/awx/ui/client/src/instance-groups/jobs/jobs.list.js index 59e14ba19b..061674054d 100644 --- a/awx/ui/client/src/instance-groups/jobs/jobs.list.js +++ b/awx/ui/client/src/instance-groups/jobs/jobs.list.js @@ -1,76 +1,76 @@ -export default ['i18n', function (i18n) { - return { - name: 'jobs', - iterator: 'job', - basePath: 'api/v2/instance_groups/{{$stateParams.instance_group_id}}/jobs/', - index: false, - hover: false, - well: true, - emptyListText: i18n._('No jobs have yet run.'), - listTitle: false, +// export default ['i18n', function (i18n) { +// return { +// name: 'jobs', +// iterator: 'job', +// basePath: 'api/v2/instance_groups/{{$stateParams.instance_group_id}}/jobs/', +// index: false, +// hover: false, +// well: true, +// emptyListText: i18n._('No jobs have yet run.'), +// listTitle: false, - fields: { - status: { - label: '', - columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus', - dataTipWatch: 'job.status_tip', - awToolTip: "{{ job.status_tip }}", - awTipPlacement: "right", - dataTitle: "{{ job.status_popover_title }}", - icon: 'icon-job-{{ job.status }}', - iconOnly: true, - ngClick: "viewjobResults(job)", - nosort: true - }, - id: { - label: i18n._('ID'), - ngClick: "viewjobResults(job)", - columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent', - awToolTip: "{{ 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(job)", - badgePlacement: 'right', - badgeCustom: true, - nosort: true, - badgeIcon: ` - - W - - ` - }, - type: { - label: i18n._('Type'), - ngBind: 'job.type_label', - 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' - }, - } - }; -}]; +// fields: { +// status: { +// label: '', +// columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus', +// dataTipWatch: 'job.status_tip', +// awToolTip: "{{ job.status_tip }}", +// awTipPlacement: "right", +// dataTitle: "{{ job.status_popover_title }}", +// icon: 'icon-job-{{ job.status }}', +// iconOnly: true, +// ngClick: "viewjobResults(job)", +// nosort: true +// }, +// id: { +// label: i18n._('ID'), +// ngClick: "viewjobResults(job)", +// columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent', +// awToolTip: "{{ 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(job)", +// badgePlacement: 'right', +// badgeCustom: true, +// nosort: true, +// badgeIcon: ` +// +// W +// +// ` +// }, +// type: { +// label: i18n._('Type'), +// ngBind: 'job.type_label', +// 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' +// }, +// } +// }; +// }]; diff --git a/awx/ui/client/src/instance-groups/jobs/jobs.strings.js b/awx/ui/client/src/instance-groups/jobs/jobs.strings.js new file mode 100644 index 0000000000..099f7a61b9 --- /dev/null +++ b/awx/ui/client/src/instance-groups/jobs/jobs.strings.js @@ -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; diff --git a/awx/ui/client/src/instance-groups/jobs/list.view.html b/awx/ui/client/src/instance-groups/jobs/list.view.html new file mode 100644 index 0000000000..bd36b97548 --- /dev/null +++ b/awx/ui/client/src/instance-groups/jobs/list.view.html @@ -0,0 +1,103 @@ + + + {{ vm.panelTitle }} + + + {{:: vm.strings.get('tab.DETAILS') }} + {{:: vm.strings.get('tab.INSTANCES') }} + {{:: vm.strings.get('tab.JOBS') }} + + + +
+ + +
+ + + +
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+
+
+ + +
+
diff --git a/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js b/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js index 381e2419bf..4f1145ee1c 100644 --- a/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js +++ b/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js @@ -1,19 +1,43 @@ -export default ['$scope', 'InstanceGroupList', 'GetBasePath', 'Rest', 'Dataset','Find', '$state', - function($scope, InstanceGroupList, GetBasePath, Rest, Dataset, Find, $state) { +export default ['$scope', 'InstanceGroupList', 'resolvedModels', 'GetBasePath', 'Rest', 'Dataset','Find', '$state', '$q', 'ComponentsStrings', + function($scope, InstanceGroupList, resolvedModels, GetBasePath, Rest, Dataset, Find, $state, $q, strings) { let list = InstanceGroupList; + const vm = this; + const { instanceGroup } = resolvedModels; init(); function init(){ + vm.panelTitle = strings.get('layout.INSTANCE_GROUPS'); $scope.list = list; $scope[`${list.iterator}_dataset`] = Dataset.data; $scope[list.name] = $scope[`${list.iterator}_dataset`].results; $scope.instanceGroupCount = Dataset.data.count; } - $scope.isActive = function(id) { - let selected = parseInt($state.params.instance_group_id); - return id === selected; + $scope.selection = {}; + + $scope.$watch('$state.params.instance_group_id', () => { + vm.activeId = parseInt($state.params.instance_group_id); + }); + + vm.delete = () => { + let deletables = $scope.selection; + deletables = Object.keys(deletables).filter((n) => deletables[n]); + //refactor + deletables.forEach((data) => { + let promise = instanceGroup.http.delete({resource: data}) + Promise.resolve(promise).then(vm.onSaveSuccess); + }); + } + + vm.onSaveSuccess = () => { + $state.transitionTo($state.current, $state.params, { + reload: true, location: true, inherit: false, notify: true + }); + } + + $scope.createInstanceGroup = () => { + $state.go('instanceGroups.add'); }; } -]; \ No newline at end of file +]; diff --git a/awx/ui/client/src/instance-groups/list/instance-groups-list.partial.html b/awx/ui/client/src/instance-groups/list/instance-groups-list.partial.html index f3d470afd9..ae2f5df32d 100644 --- a/awx/ui/client/src/instance-groups/list/instance-groups-list.partial.html +++ b/awx/ui/client/src/instance-groups/list/instance-groups-list.partial.html @@ -1,63 +1,84 @@ -
-
-
- INSTANCE GROUPS -
+ + + {{ vm.panelTitle }} {{ instanceGroupCount }} -
-
+ - - + +
+ + + +
+
+ +
+ +
+
-
PLEASE ADD ITEMS TO THIS LIST
+ + -
- - - - - - - - - - - - - - - - -
- "{{'Name' | translate}}" - - - Running Jobs - - Used Capacity -
- {{ instance_group.name }} - {{ instance_group.instances }} - - - {{ instance_group.jobs_running }} - - - -
-
+ + +
+ + + +
+ + + + + +
+ +
+ +
+ +
+
+
+
+ + + - - diff --git a/awx/ui/client/src/instance-groups/main.js b/awx/ui/client/src/instance-groups/main.js index 4f9410f0e0..8489cf83f5 100644 --- a/awx/ui/client/src/instance-groups/main.js +++ b/awx/ui/client/src/instance-groups/main.js @@ -1,58 +1,331 @@ import InstanceGroupsList from './list/instance-groups-list.controller'; 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 InstanceGroupJobsListController from './jobs/jobs.controller'; 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 list from './instance-groups.list'; import service from './instance-groups.service'; -export default -angular.module('instanceGroups', [CapacityBar.name]) +import { templateUrl } from '../shared/template-url/template-url.factory'; + +import addEditTemplate from './add-edit/add-edit-instance-groups.view.html'; +import addInstanceModalTemplate from './add-edit/add-instance-list-policy.partial.html'; +import addInstanceModalController from './add-edit/add-instance-list-policy.controller.js'; +import instancesTemplate from './instances/instances-list.partial.html'; +import instanceModalTemplate from './instances/instance-modal.partial.html'; +import instanceModalController from './instances/instance-modal.controller.js'; +import AddInstanceGroupController from './add-edit/add-instance-group.controller'; +import EditInstanceGroupController from './add-edit/edit-instance-group.controller'; +import InstanceGroupsStrings from './instance-groups.strings'; +import JobStrings from './jobs/jobs.strings'; + +import jobsTemplate from './jobs/list.view.html'; + +const MODULE_NAME = 'instanceGroups'; + +function InstanceGroupsResolve ($q, $stateParams, InstanceGroup, Instance, Job) { + 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', + 'JobModel' +]; + +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: templateUrl('./instance-groups/list/instance-groups-list'), + controller: 'InstanceGroupsList', + 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: ` + `, + } + } + }); + + $stateExtender.addState({ + name: 'instanceGroups.add.modal.instances', + ncyBreadcrumb: { + skip: true, + }, + views: { + "modal": { + templateUrl: addInstanceModalTemplate, + controller: addInstanceModalController, + controllerAs: 'vm' + } + }, + 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: ` + `, + } + } + }); + + $stateExtender.addState({ + name: 'instanceGroups.edit.modal.instances', + ncyBreadcrumb: { + skip: true, + }, + views: { + "modal": { + templateUrl: addInstanceModalTemplate, + controller: addInstanceModalController, + controllerAs: 'vm' + } + }, + 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: ` + `, + } + } + }); + + $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: 'InstanceJobsController', + controllerAs: 'vm' + }, + }, + params: { + job_search: { + value: { + page_size: '10', + order_by: '-finished' + }, + dynamic: true + }, + }, + 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 + } + }, + views: { + 'jobs@instanceGroups': { + templateUrl: jobsTemplate, + controller: 'InstanceGroupJobsListController', + controllerAs: 'vm' + }, + }, + resolve: { + resolvedModels: InstanceGroupsResolve + } + }) +} + +InstanceGroupsRun.$inject = [ + '$stateExtender', + 'InstanceGroupsStrings', + 'ComponentsStrings' +]; + +angular.module(MODULE_NAME, [CapacityBar.name]) .service('InstanceGroupsService', service) .factory('InstanceGroupList', list) - .factory('JobsList', JobsList) - .factory('InstanceList', InstanceList) - .factory('InstanceJobsList', InstanceJobsList) .controller('InstanceGroupsList', InstanceGroupsList) - .controller('JobsListController', JobsListController) + .controller('InstanceGroupJobsListController', InstanceGroupJobsListController) .controller('InstanceListController', InstanceListController) .controller('InstanceJobsController', InstanceJobsController) .directive('instanceGroupsMultiselect', instanceGroupsMultiselect) .directive('instanceGroupsModal', instanceGroupsModal) - .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { - let stateExtender = $stateExtenderProvider.$get(); + .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; diff --git a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.partial.html b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.partial.html index ba96157381..48eb41b1b5 100644 --- a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.partial.html +++ b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.partial.html @@ -11,6 +11,7 @@
{{selectedRow.name}} + {{selectedRow.hostname}}