diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 70efcfa1c8..610dcc31c2 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -41,9 +41,6 @@ import './helpers'; import './lists'; import './widgets'; import './filters'; -import { Home } from './controllers/Home'; -import { SocketsController } from './controllers/Sockets'; -import { CredentialsAdd, CredentialsEdit, CredentialsList } from './controllers/Credentials'; import portalMode from './portal-mode/main'; import systemTracking from './system-tracking/main'; import inventories from './inventories/main'; @@ -62,7 +59,7 @@ import mainMenu from './main-menu/main'; import breadCrumb from './bread-crumb/main'; import browserData from './browser-data/main'; import configuration from './configuration/main'; -import dashboard from './dashboard/main'; +import home from './home/main'; import moment from './shared/moment/main'; import login from './login/main'; import activityStream from './activity-stream/main'; @@ -70,9 +67,9 @@ import standardOut from './standard-out/main'; import Templates from './templates/main'; import credentials from './credentials/main'; import jobs from './jobs/main'; -import { ProjectsList, ProjectsAdd, ProjectsEdit } from './controllers/Projects'; -import { UsersList, UsersAdd, UsersEdit } from './controllers/Users'; -import { TeamsList, TeamsAdd, TeamsEdit } from './controllers/Teams'; +import teams from './teams/main'; +import users from './users/main'; +import projects from './projects/main'; import RestServices from './rest/main'; import access from './access/main'; @@ -85,7 +82,6 @@ import config from './shared/config/main'; import './login/authenticationServices/pendo/ng-pendo'; import footer from './footer/main'; import scheduler from './scheduler/main'; -import { N_ } from './i18n'; var tower = angular.module('Tower', [ // how to add CommonJS / AMD third-party dependencies: @@ -119,7 +115,7 @@ var tower = angular.module('Tower', [ setupMenu.name, mainMenu.name, breadCrumb.name, - dashboard.name, + home.name, moment.name, login.name, activityStream.name, @@ -135,6 +131,9 @@ var tower = angular.module('Tower', [ config.name, credentials.name, jobs.name, + teams.name, + users.name, + projects.name, //'templates', 'Utilities', 'OrganizationFormDefinition', @@ -227,10 +226,9 @@ var tower = angular.module('Tower', [ }); }]) .config(['$urlRouterProvider', '$breadcrumbProvider', 'QuerySetProvider', - '$urlMatcherFactoryProvider', 'stateDefinitionsProvider', '$stateProvider', + '$urlMatcherFactoryProvider', function($urlRouterProvider, $breadcrumbProvider, QuerySet, - $urlMatcherFactoryProvider, stateDefinitionsProvider, $stateProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(); + $urlMatcherFactoryProvider) { $urlMatcherFactoryProvider.strictMode(false); $breadcrumbProvider.setOptions({ templateUrl: urlPrefix + 'partials/breadcrumb.html' @@ -266,109 +264,6 @@ var tower = angular.module('Tower', [ // $stateProvider.stateRegistry.onStatesChanged((event, states) =>{ // console.log(event, states) // }) - - - // lazily generate a tree of substates which will replace this node in ui-router's stateRegistry - // see: stateDefinition.factory for usage documentation - $stateProvider.state({ - name: 'projects', - url: '/projects', - lazyLoad: () => stateDefinitions.generateTree({ - parent: 'projects', // top-most node in the generated tree (will replace this state definition) - modes: ['add', 'edit'], - list: 'ProjectList', - form: 'ProjectsForm', - controllers: { - list: ProjectsList, // DI strings or objects - add: ProjectsAdd, - edit: ProjectsEdit - }, - data: { - activityStream: true, - activityStreamTarget: 'project', - socket: { - "groups": { - "jobs": ["status_changed"] - } - } - }, - ncyBreadcrumb: { - label: N_('PROJECTS') - } - }) - }); - - $stateProvider.state({ - name: 'credentials', - url: '/credentials', - lazyLoad: () => stateDefinitions.generateTree({ - parent: 'credentials', - modes: ['add', 'edit'], - list: 'CredentialList', - form: 'CredentialForm', - controllers: { - list: CredentialsList, - add: CredentialsAdd, - edit: CredentialsEdit - }, - data: { - activityStream: true, - activityStreamTarget: 'credential' - }, - ncyBreadcrumb: { - parent: 'setup', - label: N_('CREDENTIALS') - } - }) - }); - - $stateProvider.state({ - name: 'teams', - url: '/teams', - lazyLoad: () => stateDefinitions.generateTree({ - parent: 'teams', - modes: ['add', 'edit'], - list: 'TeamList', - form: 'TeamForm', - controllers: { - list: TeamsList, - add: TeamsAdd, - edit: TeamsEdit - }, - data: { - activityStream: true, - activityStreamTarget: 'team' - }, - ncyBreadcrumb: { - parent: 'setup', - label: N_('TEAMS') - } - }) - }); - - $stateProvider.state({ - name: 'users', - url: '/users', - lazyLoad: () => stateDefinitions.generateTree({ - parent: 'users', - modes: ['add', 'edit'], - list: 'UserList', - form: 'UserForm', - controllers: { - list: UsersList, - add: UsersAdd, - edit: UsersEdit - }, - data: { - activityStream: true, - activityStreamTarget: 'user' - }, - ncyBreadcrumb: { - parent: 'setup', - label: N_('USERS') - } - }) - }); } ]) .run(['$stateExtender', '$q', '$compile', '$cookieStore', '$rootScope', '$log', '$stateParams', @@ -392,68 +287,6 @@ var tower = angular.module('Tower', [ $log.debug(`$state.defaultErrorHandler: ${error}`); }); - $stateExtender.addState({ - name: 'dashboard', - url: '/home', - templateUrl: urlPrefix + 'partials/home.html', - controller: Home, - params: { licenseMissing: null }, - data: { - activityStream: true, - refreshButton: true, - socket: { - "groups": { - "jobs": ["status_changed"] - } - }, - }, - ncyBreadcrumb: { - label: N_("DASHBOARD") - }, - resolve: { - graphData: ['$q', 'jobStatusGraphData', '$rootScope', - function($q, jobStatusGraphData, $rootScope) { - return $rootScope.featuresConfigured.promise.then(function() { - return $q.all({ - jobStatus: jobStatusGraphData.get("month", "all"), - }); - }); - } - ] - } - }); - - $stateExtender.addState({ - name: 'userCredentials', - url: '/users/:user_id/credentials', - templateUrl: urlPrefix + 'partials/users.html', - controller: CredentialsList - }); - - $stateExtender.addState({ - name: 'userCredentialAdd', - url: '/users/:user_id/credentials/add', - templateUrl: urlPrefix + 'partials/teams.html', - controller: CredentialsAdd - }); - - $stateExtender.addState({ - name: 'teamUserCredentialEdit', - url: '/teams/:user_id/credentials/:credential_id', - templateUrl: urlPrefix + 'partials/teams.html', - controller: CredentialsEdit - }); - - $stateExtender.addState({ - name: 'sockets', - url: '/sockets', - templateUrl: urlPrefix + 'partials/sockets.html', - controller: SocketsController, - ncyBreadcrumb: { - label: N_('SOCKETS') - } - }); - function activateTab() { // Make the correct tab active var base = $location.path().replace(/^\//, '').split('/')[0]; diff --git a/awx/ui/client/src/controllers/Credentials.js b/awx/ui/client/src/controllers/Credentials.js deleted file mode 100644 index 3c06b69182..0000000000 --- a/awx/ui/client/src/controllers/Credentials.js +++ /dev/null @@ -1,631 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Credentials - * @description This controller's for the credentials page - */ - - -export function CredentialsList($scope, $rootScope, $location, $log, - $stateParams, Rest, Alert, CredentialList, Prompt, ClearScope, - ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset, - i18n) { - - ClearScope(); - - var list = CredentialList, - defaultUrl = GetBasePath('credentials'); - - init(); - - function init() { - rbacUiControlService.canAdd('credentials') - .then(function(canAdd) { - $scope.canAdd = canAdd; - }); - - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.selected = []; - } - - $scope.$on(`${list.iterator}_options`, function(event, data){ - $scope.options = data.data.actions.GET; - optionsRequestDataProcessing(); - }); - - $scope.$watchCollection(`${$scope.list.name}`, function() { - optionsRequestDataProcessing(); - }); - - // iterate over the list and add fields like type label, after the - // OPTIONS request returns, or the list is sorted/paginated/searched - function optionsRequestDataProcessing(){ - if ($scope[list.name] !== undefined) { - $scope[list.name].forEach(function(item, item_idx) { - var itm = $scope[list.name][item_idx]; - - // Set the item type label - if (list.fields.kind && $scope.options && - $scope.options.hasOwnProperty('kind')) { - $scope.options.kind.choices.every(function(choice) { - if (choice[0] === item.kind) { - itm.kind_label = choice[1]; - return false; - } - return true; - }); - } - }); - } - } - - $scope.addCredential = function() { - $state.go('credentials.add'); - }; - - $scope.editCredential = function(id) { - $state.go('credentials.edit', { credential_id: id }); - }; - - $scope.deleteCredential = function(id, name) { - var action = function() { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .success(function() { - if (parseInt($state.params.credential_id) === id) { - $state.go("^", null, { reload: true }); - } else { - $state.go('.', null, {reload: true}); - } - Wait('stop'); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - body: '
" + eventData.res[fld] + "\n"; - html += "
" + txt + "\n"; - html += "
' + - i18n._('Example URLs for GIT SCM include:') + - '
' + i18n.sprintf(i18n._('%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' + - 'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' + - 'SSH. GIT read only protocol (git://) does not use username or password information.'), '', ''); - break; - case 'svn': - $scope.urlPopover = '
' + i18n._('Example URLs for Subversion SCM include:') + '
' + - '' + i18n._('Example URLs for Mercurial SCM include:') + '
' + - '' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' + - 'Do not put the username and key in the URL. ' + - 'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '', ''); - break; - default: - $scope.urlPopover = '
' + i18n._('URL popover text'); - } - } - - }; - $scope.formCancel = function() { - $state.go('projects'); - }; -} - -ProjectsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', - '$stateParams', 'GenerateForm', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', - 'GetProjectPath', 'GetChoices', 'Wait', '$state', 'CreateSelect2', 'i18n']; - - -export function ProjectsEdit($scope, $rootScope, $compile, $location, $log, - $stateParams, ProjectsForm, Rest, Alert, ProcessErrors, GenerateForm, - Prompt, ClearScope, GetBasePath, GetProjectPath, Authorization, - GetChoices, Empty, DebugForm, Wait, ProjectUpdate, $state, CreateSelect2, ToggleNotification, i18n) { - - ClearScope('htmlTemplate'); - - var form = ProjectsForm(), - defaultUrl = GetBasePath('projects') + $stateParams.project_id + '/', - master = {}, - id = $stateParams.project_id; - - init(); - - function init() { - $scope.project_local_paths = []; - $scope.base_dir = ''; - } - - $scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - if ($scope.pathsReadyRemove) { - $scope.pathsReadyRemove(); - } - $scope.pathsReadyRemove = $scope.$on('pathsReady', function () { - CreateSelect2({ - element: '#local-path-select', - multiple: false - }); - }); - - // After the project is loaded, retrieve each related set - if ($scope.projectLoadedRemove) { - $scope.projectLoadedRemove(); - } - $scope.projectLoadedRemove = $scope.$on('projectLoaded', function() { - var opts = []; - - if (Authorization.getUserInfo('is_superuser') === true) { - GetProjectPath({ scope: $scope, master: master }); - } else { - opts.push({ - label: $scope.local_path, - value: $scope.local_path - }); - $scope.project_local_paths = opts; - $scope.local_path = $scope.project_local_paths[0]; - $scope.base_dir = i18n._('You do not have access to view this property'); - $scope.$emit('pathsReady'); - } - - $scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false; - $scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false; - $scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch'; - Wait('stop'); - - $scope.scmChange(); - }); - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - let i; - for (i = 0; i < $scope.scm_type_options.length; i++) { - if ($scope.scm_type_options[i].value === '') { - $scope.scm_type_options[i].value = "manual"; - break; - } - } - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl); - Rest.get({ params: { id: id } }) - .success(function(data) { - var fld, i; - for (fld in form.fields) { - if (form.fields[fld].type === 'checkbox_group') { - for (i = 0; i < form.fields[fld].fields.length; i++) { - $scope[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name]; - master[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name]; - } - } else { - if (data[fld] !== undefined) { - $scope[fld] = data[fld]; - master[fld] = data[fld]; - } - } - if (form.fields[fld].sourceModel && data.summary_fields && - data.summary_fields[form.fields[fld].sourceModel]) { - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - } - } - - data.scm_type = (Empty(data.scm_type)) ? 'manual' : data.scm_type; - for (i = 0; i < $scope.scm_type_options.length; i++) { - if ($scope.scm_type_options[i].value === data.scm_type) { - $scope.scm_type = $scope.scm_type_options[i]; - break; - } - } - - if ($scope.scm_type.value !== 'manual') { - $scope.pathRequired = false; - $scope.scmRequired = true; - } else { - $scope.pathRequired = true; - $scope.scmRequired = false; - } - - master.scm_type = $scope.scm_type; - CreateSelect2({ - element: '#project_scm_type', - multiple: false - }); - - $scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch'; - $scope.scm_update_tooltip = i18n._("Start an SCM update"); - $scope.scm_type_class = ""; - if (data.status === 'running' || data.status === 'updating') { - $scope.scm_update_tooltip = i18n._("SCM update currently running"); - $scope.scm_type_class = "btn-disabled"; - } - if (Empty(data.scm_type)) { - $scope.scm_update_tooltip = i18n._('Manual projects do not require an SCM update'); - $scope.scm_type_class = "btn-disabled"; - } - - $scope.project_obj = data; - $scope.name = data.name; - $scope.$emit('projectLoaded'); - Wait('stop'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed to retrieve project: %s. GET status: '), id) + status - }); - }); - }); - - // Load the list of options for Kind - Wait('start'); - GetChoices({ - url: GetBasePath('projects'), - scope: $scope, - field: 'scm_type', - variable: 'scm_type_options', - callback: 'choicesReady' - }); - - $scope.toggleNotification = function(event, id, column) { - var notifier = this.notification; - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - ToggleNotification({ - scope: $scope, - url: $scope.project_obj.url, - notifier: notifier, - column: column, - callback: 'NotificationRefresh' - }); - }; - - // Save changes to the parent - $scope.formSave = function() { - var fld, i, params; - GenerateForm.clearApiErrors($scope); - Wait('start'); - $rootScope.flashMessage = null; - params = {}; - for (fld in form.fields) { - if (form.fields[fld].type === 'checkbox_group') { - for (i = 0; i < form.fields[fld].fields.length; i++) { - params[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name]; - } - } else { - if (form.fields[fld].type !== 'alertblock') { - params[fld] = $scope[fld]; - } - } - } - - if ($scope.scm_type.value === "manual") { - params.scm_type = ""; - params.local_path = $scope.local_path.value; - } else { - params.scm_type = $scope.scm_type.value; - delete params.local_path; - } - - Rest.setUrl(defaultUrl); - Rest.put(params) - .success(function() { - Wait('stop'); - $state.go($state.current, {}, { reload: true }); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Failed to update project: %s. PUT status: '), id) + status }); - }); - }; - - // Related set: Delete button - $scope['delete'] = function(set, itm_id, name, title) { - var action = function() { - var url = GetBasePath('projects') + id + '/' + set + '/'; - $rootScope.flashMessage = null; - Rest.setUrl(url); - Rest.post({ id: itm_id, disassociate: 1 }) - .success(function() { - $('#prompt-modal').modal('hide'); - // @issue: OLD SEARCH - // $scope.search(form.related[set].iterator); - }) - .error(function(data, status) { - $('#prompt-modal').modal('hide'); - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. POST returned status: '), url) + status }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - body: '
' + i18n._('Example URLs for GIT SCM include:') + '
' + i18n.sprintf(i18n._('%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' + - 'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' + - 'SSH. GIT read only protocol (git://) does not use username or password information.'), '', ''); - break; - case 'svn': - $scope.urlPopover = '
' + i18n._('Example URLs for Subversion SCM include:') + '
' + - '' + i18n._('Example URLs for Mercurial SCM include:') + '
' + - '' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' + - 'Do not put the username and key in the URL. ' + - 'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '', ''); - break; - default: - $scope.urlPopover = '
' + i18n._('URL popover text'); - } - } - }; - - $scope.SCMUpdate = function() { - if ($scope.project_obj.scm_type === "Manual" || Empty($scope.project_obj.scm_type)) { - // ignore - } else if ($scope.project_obj.status === 'updating' || $scope.project_obj.status === 'running' || $scope.project_obj.status === 'pending') { - Alert(i18n._('Update in Progress'), i18n._('The SCM update process is running.'), 'alert-info'); - } else { - ProjectUpdate({ scope: $scope, project_id: $scope.project_obj.id }); - } - }; - - $scope.formCancel = function() { - $state.transitionTo('projects'); - }; -} - -ProjectsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', - '$stateParams', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GenerateForm', - 'Prompt', 'ClearScope', 'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty', - 'DebugForm', 'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification', 'i18n']; diff --git a/awx/ui/client/src/controllers/Schedules.js b/awx/ui/client/src/controllers/Schedules.js deleted file mode 100644 index edebf66a84..0000000000 --- a/awx/ui/client/src/controllers/Schedules.js +++ /dev/null @@ -1,86 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Schedules - * @description This controller's for schedules -*/ - - -export function ScheduleEditController($scope, $compile, $location, $stateParams, SchedulesList, Rest, ProcessErrors, ReturnToCaller, ClearScope, -GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices) { - - ClearScope(); - - var base, id, url, parentObject; - - // base = $location.path().replace(/^\//, '').split('/')[0]; - - // if ($scope.removePostRefresh) { - // $scope.removePostRefresh(); - // } - // $scope.removePostRefresh = $scope.$on('PostRefresh', function() { - // var list = $scope.schedules; - // list.forEach(function(element, idx) { - // list[idx].play_tip = (element.enabled) ? 'Schedule is Active. Click to temporarily stop.' : 'Schedule is temporarily stopped. Click to activate.'; - // }); - // }); - - // if ($scope.removeParentLoaded) { - // $scope.removeParentLoaded(); - // } - // $scope.removeParentLoaded = $scope.$on('ParentLoaded', function() { - // url += "schedules/"; - // SchedulesList.well = true; - // LoadSchedulesScope({ - // parent_scope: $scope, - // scope: $scope, - // list: SchedulesList, - // id: 'schedule-list-target', - // url: url, - // pageSize: 20 - // }); - // }); - - - if ($scope.removeChoicesReady) { - $scope.removeChocesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - // Load the parent object - id = $stateParams.id; - url = GetBasePath(base) + id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - parentObject = data; - $scope.$emit('ParentLoaded'); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status }); - }); - }); - - $scope.refreshJobs = function() { - // @issue: OLD SEARCH - // $scope.search(SchedulesList.iterator); - }; - - Wait('start'); - - GetChoices({ - scope: $scope, - url: GetBasePath('unified_jobs'), //'/static/sample/data/types/data.json' - field: 'type', - variable: 'type_choices', - callback: 'choicesReady' - }); -} - -ScheduleEditController.$inject = [ '$scope', '$compile', '$location', '$stateParams', 'SchedulesList', 'Rest', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', - 'GetBasePath', 'Wait', 'Find', 'LoadSchedulesScope', 'GetChoices']; diff --git a/awx/ui/client/src/controllers/Sockets.js b/awx/ui/client/src/controllers/Sockets.js deleted file mode 100644 index 5930b57014..0000000000 --- a/awx/ui/client/src/controllers/Sockets.js +++ /dev/null @@ -1,119 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Sockets - * @description This controller's for controlling websockets - * discuss -*/ - - -export function SocketsController ($scope, $compile, ClearScope, Socket) { - - ClearScope(); - - var test_scope = $scope.$new(), - jobs_scope = $scope.$new(), - job_events_scope = $scope.$new(), - schedules_scope = $scope.$new(), - test_socket = Socket({ scope: test_scope, endpoint: "test" }), - jobs_socket = Socket({ scope: jobs_scope, endpoint: "jobs" }), - schedules_socket = Socket({ scope: schedules_scope, endpoint: "schedules" }), - job_events_socket = Socket({ scope: job_events_scope, endpoint: "job_events" }), - e, html; - - test_scope.messages = []; - jobs_scope.messages = []; - schedules_scope.messages = []; - job_events_scope.messages = []; - - html = "
' + + i18n._('Example URLs for GIT SCM include:') + + '
' + i18n.sprintf(i18n._('%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' + + 'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' + + 'SSH. GIT read only protocol (git://) does not use username or password information.'), '', ''); + break; + case 'svn': + $scope.urlPopover = '
' + i18n._('Example URLs for Subversion SCM include:') + '
' + + '' + i18n._('Example URLs for Mercurial SCM include:') + '
' + + '' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' + + 'Do not put the username and key in the URL. ' + + 'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '', ''); + break; + default: + $scope.urlPopover = '
' + i18n._('URL popover text'); + } + } + + }; + $scope.formCancel = function() { + $state.go('projects'); + }; + } +]; diff --git a/awx/ui/client/src/projects/edit/projects-edit.controller.js b/awx/ui/client/src/projects/edit/projects-edit.controller.js new file mode 100644 index 0000000000..db4a647dd5 --- /dev/null +++ b/awx/ui/client/src/projects/edit/projects-edit.controller.js @@ -0,0 +1,297 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$compile', '$location', '$log', + '$stateParams', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GenerateForm', + 'Prompt', 'ClearScope', 'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty', + 'DebugForm', 'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification', 'i18n', + function($scope, $rootScope, $compile, $location, $log, + $stateParams, ProjectsForm, Rest, Alert, ProcessErrors, GenerateForm, + Prompt, ClearScope, GetBasePath, GetProjectPath, Authorization, + GetChoices, Empty, DebugForm, Wait, ProjectUpdate, $state, CreateSelect2, ToggleNotification, i18n) { + + ClearScope('htmlTemplate'); + + var form = ProjectsForm(), + defaultUrl = GetBasePath('projects') + $stateParams.project_id + '/', + master = {}, + id = $stateParams.project_id; + + init(); + + function init() { + $scope.project_local_paths = []; + $scope.base_dir = ''; + } + + $scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) { + if (val === false) { + $scope.canAdd = false; + } + }); + + if ($scope.pathsReadyRemove) { + $scope.pathsReadyRemove(); + } + $scope.pathsReadyRemove = $scope.$on('pathsReady', function () { + CreateSelect2({ + element: '#local-path-select', + multiple: false + }); + }); + + // After the project is loaded, retrieve each related set + if ($scope.projectLoadedRemove) { + $scope.projectLoadedRemove(); + } + $scope.projectLoadedRemove = $scope.$on('projectLoaded', function() { + var opts = []; + + if (Authorization.getUserInfo('is_superuser') === true) { + GetProjectPath({ scope: $scope, master: master }); + } else { + opts.push({ + label: $scope.local_path, + value: $scope.local_path + }); + $scope.project_local_paths = opts; + $scope.local_path = $scope.project_local_paths[0]; + $scope.base_dir = i18n._('You do not have access to view this property'); + $scope.$emit('pathsReady'); + } + + $scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false; + $scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false; + $scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch'; + Wait('stop'); + + $scope.scmChange(); + }); + + if ($scope.removeChoicesReady) { + $scope.removeChoicesReady(); + } + $scope.removeChoicesReady = $scope.$on('choicesReady', function() { + let i; + for (i = 0; i < $scope.scm_type_options.length; i++) { + if ($scope.scm_type_options[i].value === '') { + $scope.scm_type_options[i].value = "manual"; + break; + } + } + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl); + Rest.get({ params: { id: id } }) + .success(function(data) { + var fld, i; + for (fld in form.fields) { + if (form.fields[fld].type === 'checkbox_group') { + for (i = 0; i < form.fields[fld].fields.length; i++) { + $scope[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name]; + master[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name]; + } + } else { + if (data[fld] !== undefined) { + $scope[fld] = data[fld]; + master[fld] = data[fld]; + } + } + if (form.fields[fld].sourceModel && data.summary_fields && + data.summary_fields[form.fields[fld].sourceModel]) { + $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; + master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; + } + } + + data.scm_type = (Empty(data.scm_type)) ? 'manual' : data.scm_type; + for (i = 0; i < $scope.scm_type_options.length; i++) { + if ($scope.scm_type_options[i].value === data.scm_type) { + $scope.scm_type = $scope.scm_type_options[i]; + break; + } + } + + if ($scope.scm_type.value !== 'manual') { + $scope.pathRequired = false; + $scope.scmRequired = true; + } else { + $scope.pathRequired = true; + $scope.scmRequired = false; + } + + master.scm_type = $scope.scm_type; + CreateSelect2({ + element: '#project_scm_type', + multiple: false + }); + + $scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch'; + $scope.scm_update_tooltip = i18n._("Start an SCM update"); + $scope.scm_type_class = ""; + if (data.status === 'running' || data.status === 'updating') { + $scope.scm_update_tooltip = i18n._("SCM update currently running"); + $scope.scm_type_class = "btn-disabled"; + } + if (Empty(data.scm_type)) { + $scope.scm_update_tooltip = i18n._('Manual projects do not require an SCM update'); + $scope.scm_type_class = "btn-disabled"; + } + + $scope.project_obj = data; + $scope.name = data.name; + $scope.$emit('projectLoaded'); + Wait('stop'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), + msg: i18n.sprintf(i18n._('Failed to retrieve project: %s. GET status: '), id) + status + }); + }); + }); + + // Load the list of options for Kind + Wait('start'); + GetChoices({ + url: GetBasePath('projects'), + scope: $scope, + field: 'scm_type', + variable: 'scm_type_options', + callback: 'choicesReady' + }); + + $scope.toggleNotification = function(event, id, column) { + var notifier = this.notification; + try { + $(event.target).tooltip('hide'); + } catch (e) { + // ignore + } + ToggleNotification({ + scope: $scope, + url: $scope.project_obj.url, + notifier: notifier, + column: column, + callback: 'NotificationRefresh' + }); + }; + + // Save changes to the parent + $scope.formSave = function() { + var fld, i, params; + GenerateForm.clearApiErrors($scope); + Wait('start'); + $rootScope.flashMessage = null; + params = {}; + for (fld in form.fields) { + if (form.fields[fld].type === 'checkbox_group') { + for (i = 0; i < form.fields[fld].fields.length; i++) { + params[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name]; + } + } else { + if (form.fields[fld].type !== 'alertblock') { + params[fld] = $scope[fld]; + } + } + } + + if ($scope.scm_type.value === "manual") { + params.scm_type = ""; + params.local_path = $scope.local_path.value; + } else { + params.scm_type = $scope.scm_type.value; + delete params.local_path; + } + + Rest.setUrl(defaultUrl); + Rest.put(params) + .success(function() { + Wait('stop'); + $state.go($state.current, {}, { reload: true }); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Failed to update project: %s. PUT status: '), id) + status }); + }); + }; + + // Related set: Delete button + $scope['delete'] = function(set, itm_id, name, title) { + var action = function() { + var url = GetBasePath('projects') + id + '/' + set + '/'; + $rootScope.flashMessage = null; + Rest.setUrl(url); + Rest.post({ id: itm_id, disassociate: 1 }) + .success(function() { + $('#prompt-modal').modal('hide'); + // @issue: OLD SEARCH + // $scope.search(form.related[set].iterator); + }) + .error(function(data, status) { + $('#prompt-modal').modal('hide'); + ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. POST returned status: '), url) + status }); + }); + }; + + Prompt({ + hdr: i18n._('Delete'), + body: '
' + i18n._('Example URLs for GIT SCM include:') + '
' + i18n.sprintf(i18n._('%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' + + 'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' + + 'SSH. GIT read only protocol (git://) does not use username or password information.'), '', ''); + break; + case 'svn': + $scope.urlPopover = '
' + i18n._('Example URLs for Subversion SCM include:') + '
' + + '' + i18n._('Example URLs for Mercurial SCM include:') + '
' + + '' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' + + 'Do not put the username and key in the URL. ' + + 'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '', ''); + break; + default: + $scope.urlPopover = '
' + i18n._('URL popover text'); + } + } + }; + + $scope.SCMUpdate = function() { + if ($scope.project_obj.scm_type === "Manual" || Empty($scope.project_obj.scm_type)) { + // ignore + } else if ($scope.project_obj.status === 'updating' || $scope.project_obj.status === 'running' || $scope.project_obj.status === 'pending') { + Alert(i18n._('Update in Progress'), i18n._('The SCM update process is running.'), 'alert-info'); + } else { + ProjectUpdate({ scope: $scope, project_id: $scope.project_obj.id }); + } + }; + + $scope.formCancel = function() { + $state.transitionTo('projects'); + }; + } +]; diff --git a/awx/ui/client/src/projects/list/projects-list.controller.js b/awx/ui/client/src/projects/list/projects-list.controller.js new file mode 100644 index 0000000000..6ecb5eb7fa --- /dev/null +++ b/awx/ui/client/src/projects/list/projects-list.controller.js @@ -0,0 +1,299 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', + 'Rest', 'Alert', 'ProjectList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', + 'GetBasePath', 'ProjectUpdate', 'Wait', 'GetChoices', 'Empty', 'Find', 'GetProjectIcon', + 'GetProjectToolTip', '$filter', '$state', 'rbacUiControlService', 'Dataset', 'i18n', 'QuerySet', + function($scope, $rootScope, $location, $log, $stateParams, + Rest, Alert, ProjectList, Prompt, ReturnToCaller, ClearScope, ProcessErrors, + GetBasePath, ProjectUpdate, Wait, GetChoices, Empty, Find, GetProjectIcon, + GetProjectToolTip, $filter, $state, rbacUiControlService, Dataset, i18n, qs) { + + var list = ProjectList, + defaultUrl = GetBasePath('projects'); + + init(); + + function init() { + $scope.canAdd = false; + + rbacUiControlService.canAdd('projects') + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + + // search init + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + + _.forEach($scope[list.name], buildTooltips); + $rootScope.flashMessage = null; + } + + $scope.$on(`${list.iterator}_options`, function(event, data){ + $scope.options = data.data.actions.GET; + optionsRequestDataProcessing(); + }); + + $scope.$watchCollection(`${$scope.list.name}`, function() { + optionsRequestDataProcessing(); + } + ); + + // iterate over the list and add fields like type label, after the + // OPTIONS request returns, or the list is sorted/paginated/searched + function optionsRequestDataProcessing(){ + if ($scope[list.name] !== undefined) { + $scope[list.name].forEach(function(item, item_idx) { + var itm = $scope[list.name][item_idx]; + + // Set the item type label + if (list.fields.scm_type && $scope.options && + $scope.options.hasOwnProperty('scm_type')) { + $scope.options.scm_type.choices.every(function(choice) { + if (choice[0] === item.scm_type) { + itm.type_label = choice[1]; + return false; + } + return true; + }); + } + + buildTooltips(itm); + + }); + } + } + + function buildTooltips(project) { + project.statusIcon = GetProjectIcon(project.status); + project.statusTip = GetProjectToolTip(project.status); + project.scm_update_tooltip = i18n._("Start an SCM update"); + project.scm_schedule_tooltip = i18n._("Schedule future SCM updates"); + project.scm_type_class = ""; + + if (project.status === 'failed' && project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') { + project.statusTip = i18n._('Canceled. Click for details'); + } + + if (project.status === 'running' || project.status === 'updating') { + project.scm_update_tooltip = i18n._("SCM update currently running"); + project.scm_type_class = "btn-disabled"; + } + if (project.scm_type === 'manual') { + project.scm_update_tooltip = i18n._('Manual projects do not require an SCM update'); + project.scm_schedule_tooltip = i18n._('Manual projects do not require a schedule'); + project.scm_type_class = 'btn-disabled'; + project.statusTip = i18n._('Not configured for SCM'); + project.statusIcon = 'none'; + } + } + + $scope.reloadList = function(){ + let path = GetBasePath(list.basePath) || GetBasePath(list.name); + qs.search(path, $stateParams[`${list.iterator}_search`]) + .then(function(searchResponse) { + $scope[`${list.iterator}_dataset`] = searchResponse.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + }); + }; + + $scope.$on(`ws-jobs`, function(e, data) { + var project; + $log.debug(data); + if ($scope.projects) { + // Assuming we have a list of projects available + project = Find({ list: $scope.projects, key: 'id', val: data.project_id }); + if (project) { + // And we found the affected project + $log.debug('Received event for project: ' + project.name); + $log.debug('Status changed to: ' + data.status); + if (data.status === 'successful' || data.status === 'failed') { + $scope.reloadList(); + } else { + project.scm_update_tooltip = "SCM update currently running"; + project.scm_type_class = "btn-disabled"; + } + project.status = data.status; + project.statusIcon = GetProjectIcon(data.status); + project.statusTip = GetProjectToolTip(data.status); + } + } + }); + + $scope.addProject = function() { + $state.go('projects.add'); + }; + + $scope.editProject = function(id) { + $state.go('projects.edit', { project_id: id }); + }; + + if ($scope.removeGoToJobDetails) { + $scope.removeGoToJobDetails(); + } + $scope.removeGoToJobDetails = $scope.$on('GoToJobDetails', function(e, data) { + if (data.summary_fields.current_update || data.summary_fields.last_update) { + + Wait('start'); + + // Grab the id from summary_fields + var id = (data.summary_fields.current_update) ? data.summary_fields.current_update.id : data.summary_fields.last_update.id; + + $state.go('scmUpdateStdout', { id: id }); + + } else { + Alert(i18n._('No Updates Available'), i18n._('There is no SCM update information available for this project. An update has not yet been ' + + ' completed. If you have not already done so, start an update for this project.'), 'alert-info'); + } + }); + + $scope.showSCMStatus = function(id) { + // Refresh the project list + var project = Find({ list: $scope.projects, key: 'id', val: id }); + if (Empty(project.scm_type) || project.scm_type === 'Manual') { + Alert(i18n._('No SCM Configuration'), i18n._('The selected project is not configured for SCM. To configure for SCM, edit the project and provide SCM settings, ' + + 'and then run an update.'), 'alert-info'); + } else { + // Refresh what we have in memory to insure we're accessing the most recent status record + Rest.setUrl(project.url); + Rest.get() + .success(function(data) { + $scope.$emit('GoToJobDetails', data); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), + msg: i18n._('Project lookup failed. GET returned: ') + status }); + }); + } + }; + + $scope.deleteProject = function(id, name) { + var action = function() { + $('#prompt-modal').modal('hide'); + Wait('start'); + var url = defaultUrl + id + '/'; + Rest.setUrl(url); + Rest.destroy() + .success(function() { + if (parseInt($state.params.project_id) === id) { + $state.go("^", null, { reload: true }); + } else { + $state.go('.', null, {reload: true}); + } + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), + msg: i18n.sprintf(i18n._('Call to %s failed. DELETE returned status: '), url) + status }); + }) + .finally(function() { + Wait('stop'); + }); + }; + + Prompt({ + hdr: i18n._('Delete'), + body: '