diff --git a/awx/ui/client/legacy/styles/forms.less b/awx/ui/client/legacy/styles/forms.less index f36914df31..cb8e9c726e 100644 --- a/awx/ui/client/legacy/styles/forms.less +++ b/awx/ui/client/legacy/styles/forms.less @@ -209,6 +209,9 @@ width: 100% !important; padding-right: 0px !important; } +.containerGroups-codeMirror{ + margin: 20px; +} .Form-formGroup--checkbox{ display: flex; diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 13632d8eb3..43810d1796 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -88,17 +88,14 @@ function AtInputLookupController (baseInputController, $q, $state) { scope.state._value = null; return vm.check({ isValid: true }); } - searchParams = searchParams || { [search.key]: scope.state._displayValue }; return model.search(searchParams, search.config) .then(found => { if (!found) { vm.reset(); - return; } - scope[scope.state._resource] = model.get('id'); scope.state._value = model.get('id'); scope.state._displayValue = model.get('name'); diff --git a/awx/ui/client/lib/components/list/_index.less b/awx/ui/client/lib/components/list/_index.less index 48d7bdb2b9..b84d220809 100644 --- a/awx/ui/client/lib/components/list/_index.less +++ b/awx/ui/client/lib/components/list/_index.less @@ -241,6 +241,10 @@ align-items: center; } +.at-Row-rightSide{ + justify-content: flex-end; +} + .at-Row-container--wrapped { display: flex; flex-wrap: wrap; diff --git a/awx/ui/client/src/instance-groups/container-groups/add-container-group.controller.js b/awx/ui/client/src/instance-groups/container-groups/add-container-group.controller.js new file mode 100644 index 0000000000..094d390071 --- /dev/null +++ b/awx/ui/client/src/instance-groups/container-groups/add-container-group.controller.js @@ -0,0 +1,62 @@ +function AddContainerGroupController(ToJSON, $scope, $state, models, strings, i18n, DataSet) { + const vm = this || {}; + const { + instanceGroup, + credential + } = models; + + vm.mode = 'add'; + vm.strings = strings; + vm.panelTitle = strings.get('state.ADD_CONTAINER_GROUP_BREADCRUMB_LABEL'); + vm.lookUpTitle = strings.get('container.LOOK_UP_TITLE'); + + vm.form = instanceGroup.createFormSchema('post'); + vm.form.name.required = true; + + vm.form.credential = { + type: 'field', + label: i18n._('Credential'), + id: 'credential' + }; + vm.form.credential._resource = 'credential'; + vm.form.credential._route = "instanceGroups.addContainerGroup.credentials"; + vm.form.credential._model = credential; + vm.form.credential._placeholder = strings.get('container.CREDENTIAL_PLACEHOLDER'); + vm.form.credential.required = true; + + vm.form.extraVars = { + label: strings.get('container.POD_SPEC_LABEL'), + value: DataSet.data.actions.POST.pod_spec_override.default, + name: 'extraVars' + }; + + $scope.variables = vm.form.extraVars.value; + $scope.name = vm.form.extraVars.name; + vm.panelTitle = strings.get('container.PANEL_TITLE'); + + + $scope.$watch('credential', () => { + if ($scope.credential) { + vm.form.credential._idFromModal= $scope.credential; + } + }); + vm.form.save = (data) => { + data.pod_spec_override = vm.form.extraVars.value; + return instanceGroup.request('post', { data: data }).then((res) => { + $state.go('instanceGroups.editContainerGroup', { instance_group_id: res.data.id }, { reload: true }); + }); + }; +} + + +AddContainerGroupController.$inject = [ + 'ToJSON', + '$scope', + '$state', + 'resolvedModels', + 'InstanceGroupsStrings', + 'i18n', + 'DataSet' +]; + +export default AddContainerGroupController; diff --git a/awx/ui/client/src/instance-groups/container-groups/add-container-group.view.html b/awx/ui/client/src/instance-groups/container-groups/add-container-group.view.html new file mode 100644 index 0000000000..1d7f779487 --- /dev/null +++ b/awx/ui/client/src/instance-groups/container-groups/add-container-group.view.html @@ -0,0 +1,22 @@ +
+ + + + + + + + + + + + + + + diff --git a/awx/ui/client/src/instance-groups/container-groups/edit-container-group.controller.js b/awx/ui/client/src/instance-groups/container-groups/edit-container-group.controller.js new file mode 100644 index 0000000000..463b289947 --- /dev/null +++ b/awx/ui/client/src/instance-groups/container-groups/edit-container-group.controller.js @@ -0,0 +1,65 @@ +function EditContainerGroupController($rootScope, $scope, $state, models, strings, i18n, EditContainerGroupDataset) { + + const vm = this || {}; + const { + instanceGroup, + credential + } = models; + $rootScope.breadcrumb.instance_group_name = instanceGroup.get('name'); + + vm.mode = 'add'; + vm.strings = strings; + vm.panelTitle = EditContainerGroupDataset.data.name; + vm.lookUpTitle = strings.get('container.LOOK_UP_TITLE'); + + vm.form = instanceGroup.createFormSchema('post'); + vm.form.name.required = true; + vm.form.credential = { + type: 'field', + label: i18n._('Credential'), + id: 'credential' + }; + vm.form.credential._resource = 'credential'; + vm.form.credential._route = "instanceGroups.editContainerGroup.credentials"; + vm.form.credential._model = credential; + vm.form.credential._displayValue = EditContainerGroupDataset.data.summary_fields.credential.name; + vm.form.credential.required = true; + vm.form.credential._value = EditContainerGroupDataset.data.summary_fields.credential.id; + vm.podSpec = { + type: 'textarea', + id: 'pod_spec' + }; + + vm.podSpec.label = strings.get('container.POD_SPEC_LABEL'); + + vm.form.extraVars = { + label: strings.get('container.POD_SPEC_LABEL'), + value: EditContainerGroupDataset.data.pod_spec_override, + name: 'extraVars' + }; + + $scope.$watch('credential', () => { + if ($scope.credential) { + vm.form.credential._idFromModal= $scope.credential; + } + }); + vm.form.save = (data) => { + data.pod_spec_override = vm.form.extraVars.value; + return instanceGroup.request('put', { data: data }).then((res) => { + $state.go('instanceGroups.editContainerGroup', { instance_group_id: res.data.id }, { reload: true }); + } ); + + }; +} + +EditContainerGroupController.$inject = [ + '$rootScope', + '$scope', + '$state', + 'resolvedModels', + 'InstanceGroupsStrings', + 'i18n', + 'EditContainerGroupDataset' +]; + +export default EditContainerGroupController; diff --git a/awx/ui/client/src/instance-groups/instance-group.block.less b/awx/ui/client/src/instance-groups/instance-group.block.less index ec5279eb8d..eedfd05e94 100644 --- a/awx/ui/client/src/instance-groups/instance-group.block.less +++ b/awx/ui/client/src/instance-groups/instance-group.block.less @@ -1,4 +1,8 @@ .InstanceGroups { + .at-Row-actions{ + width: 700px; + justify-content: flex-end; + } .BreadCrumb-menuLinkImage:hover { color: @default-link; @@ -75,3 +79,8 @@ } } } +.at-Row-links { +justify-content: flex-end; +display: flex; +flex-grow: 1; +} 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 562676ffdc..7bec7b2624 100644 --- a/awx/ui/client/src/instance-groups/instance-groups.partial.html +++ b/awx/ui/client/src/instance-groups/instance-groups.partial.html @@ -2,6 +2,8 @@
+
+
diff --git a/awx/ui/client/src/instance-groups/instance-groups.strings.js b/awx/ui/client/src/instance-groups/instance-groups.strings.js index ad4fff924b..2f3d9865ab 100644 --- a/awx/ui/client/src/instance-groups/instance-groups.strings.js +++ b/awx/ui/client/src/instance-groups/instance-groups.strings.js @@ -1,13 +1,16 @@ -function InstanceGroupsStrings (BaseString) { +function InstanceGroupsStrings(BaseString) { BaseString.call(this, 'instanceGroups'); - const { t } = this; + const { + t + } = this; const ns = this.instanceGroups; ns.state = { INSTANCE_GROUPS_BREADCRUMB_LABEL: t.s('INSTANCE GROUPS'), INSTANCES_BREADCRUMB_LABEL: t.s('INSTANCES'), - ADD_BREADCRUMB_LABEL: t.s('CREATE INSTANCE GROUP') + ADD_BREADCRUMB_LABEL: t.s('CREATE INSTANCE GROUP'), + ADD_CONTAINER_GROUP_BREADCRUMB_LABEL: t.s('CREATE CONTAINER GROUP') }; ns.list = { @@ -33,7 +36,8 @@ function InstanceGroupsStrings (BaseString) { }; ns.instance = { - PANEL_TITLE: t.s('SELECT INSTANCE') + PANEL_TITLE: t.s('SELECT INSTANCE'), + BADGE_TEXT: t.s('Instance Group') }; ns.capacityBar = { @@ -62,6 +66,13 @@ function InstanceGroupsStrings (BaseString) { ns.alert = { MISSING_PARAMETER: t.s('Instance Group parameter is missing.'), }; + ns.container = { + PANEL_TITLE: t.s('Add Container Group'), + LOOK_UP_TITLE: t.s('Add Credential'), + CREDENTIAL_PLACEHOLDER: t.s('SELECT A CREDENTIAL'), + POD_SPEC_LABEL: t.s('Pod Spec Override'), + BADGE_TEXT: t.s('Container Group') + }; } InstanceGroupsStrings.$inject = ['BaseStringService']; 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 2a3314ed3b..cac555dbb0 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 @@ -18,19 +18,20 @@ search-tags="searchTags">
- +
+ - + ng-class="{'at-Row--active': (instance_group.id === vm.activeId)}" >
- - - -
- - - - - - +
+
+ + + + +
+
+
+ {{vm.strings.get('container.BADGE_TEXT') }} +
+
+
+
+
+
+ {{vm.strings.get('instance.BADGE_TEXT') }} +
+
+
+
+ +
+ + + +
- -
- - - -
diff --git a/awx/ui/client/src/instance-groups/main.js b/awx/ui/client/src/instance-groups/main.js index bfd33a9c55..78858592f1 100644 --- a/awx/ui/client/src/instance-groups/main.js +++ b/awx/ui/client/src/instance-groups/main.js @@ -1,5 +1,10 @@ -import { templateUrl } from '../shared/template-url/template-url.factory'; +import { + templateUrl +} from '../shared/template-url/template-url.factory'; import CapacityAdjuster from './capacity-adjuster/capacity-adjuster.directive'; +import AddContainerGroup from './container-groups/add-container-group.view.html'; +import EditContainerGroupController from './container-groups/edit-container-group.controller'; +import AddContainerGroupController from './container-groups/add-container-group.controller'; 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'; @@ -25,34 +30,55 @@ import InstanceGroupsStrings from './instance-groups.strings'; import instanceGroupJobsRoute from '~features/jobs/routes/instanceGroupJobs.route.js'; import instanceJobsRoute from '~features/jobs/routes/instanceJobs.route.js'; + const MODULE_NAME = 'instanceGroups'; -function InstanceGroupsResolve ($q, $stateParams, InstanceGroup, Instance, ProcessErrors, strings) { +function InstanceGroupsResolve($q, $stateParams, InstanceGroup, Credential, Instance, ProcessErrors, strings) { const instanceGroupId = $stateParams.instance_group_id; const instanceId = $stateParams.instance_id; let promises = {}; if (!instanceGroupId && !instanceId) { promises.instanceGroup = new InstanceGroup(['get', 'options']); + promises.credential = new Credential(['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"}})); + .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')); + .then((instanceGroup) => instanceGroup.extend('get', 'jobs', { + params: { + page_size: "10", + order_by: "-finished" + } + })) + .then((instanceGroup) => instanceGroup.extend('get', 'instances')); + + promises.credential = new Credential(); return $q.all(promises) .then(models => models) - .catch(({ data, status, config }) => { + .catch(({ + data, + status, + config + }) => { ProcessErrors(null, data, status, null, { hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${config.url}`, status }) + msg: strings.get('error.CALL', { + path: `${config.url}`, + status + }) }); return $q.reject(); }); @@ -62,12 +88,13 @@ InstanceGroupsResolve.$inject = [ '$q', '$stateParams', 'InstanceGroupModel', + 'CredentialModel', 'InstanceModel', 'ProcessErrors', 'InstanceGroupsStrings' ]; -function InstanceGroupsRun ($stateExtender, strings) { +function InstanceGroupsRun($stateExtender, strings) { $stateExtender.addState({ name: 'instanceGroups', url: '/instance_groups', @@ -100,7 +127,7 @@ function InstanceGroupsRun ($stateExtender, strings) { resolve: { resolvedModels: InstanceGroupsResolve, Dataset: ['InstanceGroupList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { + function (list, qs, $stateParams, GetBasePath) { let path = GetBasePath(list.basePath) || GetBasePath(list.name); return qs.search(path, $stateParams[`${list.iterator}_search`]); } @@ -143,6 +170,165 @@ function InstanceGroupsRun ($stateExtender, strings) { ] } }); + $stateExtender.addState({ + name: 'instanceGroups.addContainerGroup', + url: '/container_group', + views: { + 'addContainerGroup@instanceGroups': { + templateUrl: AddContainerGroup, + controller: AddContainerGroupController, + controllerAs: 'vm' + } + }, + resolve: { + resolvedModels: InstanceGroupsResolve, + DataSet: ['Rest', 'GetBasePath', (Rest, GetBasePath) => { + Rest.setUrl(`${GetBasePath('instance_groups')}`); + return Rest.options(); + }] + }, + ncyBreadcrumb: { + label: strings.get('state.ADD_CONTAINER_GROUP_BREADCRUMB_LABEL') + }, + }); + + $stateExtender.addState({ + name: 'instanceGroups.addContainerGroup.credentials', + url: '/credential?selected', + searchPrefix: 'credential', + params: { + credential_search: { + value: { + order_by: 'name', + page_size: 5, + }, + dynamic: true, + squash: '' + } + }, + data: { + basePath: 'credentials', + formChildState: true + }, + ncyBreadcrumb: { + skip: true + }, + views: { + 'credentials@instanceGroups.addContainerGroup': { + templateProvider: (ListDefinition, generateList) => { + const html = generateList.build({ + mode: 'lookup', + list: ListDefinition, + input_type: 'radio' + }); + return `${html}`; + } + } + }, + resolve: { + ListDefinition: ['CredentialList', list => list], + Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => { + const params = { + credential_type__kind: 'kubernetes', + }; + const searchPath = GetBasePath('credentials'); + return qs.search( + searchPath, params, + $stateParams[`${list.iterator}_search`] + ); + }] + }, + onExit ($state) { + if ($state.transition) { + $('#form-modal').modal('hide'); + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + } + } + }); + + + + + $stateExtender.addState({ + name: 'instanceGroups.editContainerGroup', + url: '/container_group/edit/:instance_group_id', + views: { + 'editContainerGroup@instanceGroups': { + templateUrl: AddContainerGroup, + controller: EditContainerGroupController, + controllerAs: 'vm' + } + }, + resolve: { + resolvedModels: InstanceGroupsResolve, + EditContainerGroupDataset: ['GetBasePath', 'QuerySet', '$stateParams', + function (GetBasePath, qs, $stateParams) { + let path = `${GetBasePath('instance_groups')}${$stateParams.instance_group_id}`; + return qs.search(path, $stateParams); + } + ], + }, + ncyBreadcrumb: { + label: '{{breadcrumb.instance_group_name}}' + }, + }); + + $stateExtender.addState({ + name: 'instanceGroups.editContainerGroup.credentials', + url: '/credential?selected', + searchPrefix: 'credential', + params: { + credential_search: { + value: { + order_by: 'name', + page_size: 5, + }, + dynamic: true, + squash: '' + } + }, + data: { + basePath: 'credentials', + formChildState: true + }, + views: { + 'credentials@instanceGroups.editContainerGroup': { + templateProvider: (ListDefinition, generateList) => { + const html = generateList.build({ + mode: 'lookup', + list: ListDefinition, + input_type: 'radio' + }); + return `${html}`; + } + } + }, + resolve: { + ListDefinition: ['CredentialList', list => list], + Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => { + const params = { + credential_type__kind: 'kubernetes', + }; + const searchPath = GetBasePath('credentials'); + return qs.search( + searchPath, params, + $stateParams[`${list.iterator}_search`] + ); + }] + }, + onExit ($state) { + if ($state.transition) { + $('#form-modal').modal('hide'); + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + } + } + }); + + + + $stateExtender.addState({ name: 'instanceGroups.edit', @@ -207,7 +393,7 @@ function InstanceGroupsRun ($stateExtender, strings) { resolve: { resolvedModels: InstanceGroupsResolve, Dataset: ['GetBasePath', 'QuerySet', '$stateParams', - function(GetBasePath, qs, $stateParams) { + function (GetBasePath, qs, $stateParams) { let path = `${GetBasePath('instance_groups')}${$stateParams.instance_group_id}/instances`; return qs.search(path, $stateParams[`instance_search`]); } @@ -255,7 +441,7 @@ function InstanceGroupsRun ($stateExtender, strings) { resolve: { resolvedModels: InstanceGroupsResolve, Dataset: ['GetBasePath', 'QuerySet', '$stateParams', - function(GetBasePath, qs, $stateParams) { + function (GetBasePath, qs, $stateParams) { let path = `${GetBasePath('instances')}`; return qs.search(path, $stateParams[`add_instance_search`]); } @@ -269,7 +455,8 @@ function InstanceGroupsRun ($stateExtender, strings) { InstanceGroupsRun.$inject = [ '$stateExtender', - 'InstanceGroupsStrings' + 'InstanceGroupsStrings', + 'Rest' ]; angular.module(MODULE_NAME, []) diff --git a/awx/ui/client/src/shared/lookup/lookup-modal.directive.js b/awx/ui/client/src/shared/lookup/lookup-modal.directive.js index bfc362a257..cee16d2e1c 100644 --- a/awx/ui/client/src/shared/lookup/lookup-modal.directive.js +++ b/awx/ui/client/src/shared/lookup/lookup-modal.directive.js @@ -74,7 +74,7 @@ export default ['templateUrl', 'i18n', function(templateUrl, i18n) { } } - $scope.saveForm = function() { + $scope.saveForm = function () { eventService.remove(listeners); let list = $scope.list; if($scope.currentSelection.name !== null) { @@ -89,7 +89,7 @@ export default ['templateUrl', 'i18n', function(templateUrl, i18n) { $state.go('^'); }; - $scope.toggle_row = function(selectedRow) { + $scope.toggle_row = function (selectedRow) { let list = $scope.list; let count = 0; $scope[list.name].forEach(function(row) {