From 703f4925cb100d1c0c5386286e2ea33f357956cb Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Sun, 4 Dec 2016 21:21:22 -0500 Subject: [PATCH 01/39] Bumping versions to 3.2.0 --- awx/__init__.py | 2 +- awx/api/metadata.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/awx/__init__.py b/awx/__init__.py index f5457b5caa..fe3cf8b31c 100644 --- a/awx/__init__.py +++ b/awx/__init__.py @@ -5,7 +5,7 @@ import os import sys import warnings -__version__ = '3.1.0' +__version__ = '3.2.0' __all__ = ['__version__'] diff --git a/awx/api/metadata.py b/awx/api/metadata.py index 6dd186c9ef..da0aef79f3 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -166,7 +166,8 @@ class Metadata(metadata.SimpleMetadata): # Add version number in which view was added to Tower. added_in_version = '1.2' - for version in ('3.1.0', '3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'): + for version in ('3.2.0', '3.1.0', '3.0.0', '2.4.0', '2.3.0', '2.2.0', + '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'): if getattr(view, 'new_in_%s' % version.replace('.', ''), False): added_in_version = version break From ce55b44a7659bb2169185a8c3c9638e0ba727276 Mon Sep 17 00:00:00 2001 From: Graham Mainwaring Date: Mon, 5 Dec 2016 16:25:11 -0500 Subject: [PATCH 02/39] Move npm dependencies to Ansible github org --- awx/ui/npm-shrinkwrap.json | 51 +++++++++++++++++++++++++------------- awx/ui/package.json | 20 ++++++++------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/awx/ui/npm-shrinkwrap.json b/awx/ui/npm-shrinkwrap.json index 240a18f337..04f81e0e75 100644 --- a/awx/ui/npm-shrinkwrap.json +++ b/awx/ui/npm-shrinkwrap.json @@ -61,13 +61,13 @@ }, "angular-breadcrumb": { "version": "0.4.1", - "from": "leigh-johnson/angular-breadcrumb#0.4.1", - "resolved": "git://github.com/leigh-johnson/angular-breadcrumb.git#6c2b1ad45ad5fbe7adf39af1ef3b294ca8e207a9" + "from": "ansible/angular-breadcrumb#0.4.1", + "resolved": "git://github.com/ansible/angular-breadcrumb.git#6c2b1ad45ad5fbe7adf39af1ef3b294ca8e207a9" }, "angular-codemirror": { "version": "1.0.4", - "from": "chouseknecht/angular-codemirror#1.0.4", - "resolved": "git://github.com/chouseknecht/angular-codemirror.git#75c3a2d0ccdf2e4c836fab7d7617d5db6c585c1b", + "from": "ansible/angular-codemirror#1.0.4", + "resolved": "git://github.com/ansible/angular-codemirror.git#75c3a2d0ccdf2e4c836fab7d7617d5db6c585c1b", "dependencies": { "angular": { "version": "1.4.7", @@ -83,8 +83,8 @@ }, "angular-drag-and-drop-lists": { "version": "1.4.0", - "from": "leigh-johnson/angular-drag-and-drop-lists#1.4.0", - "resolved": "git://github.com/leigh-johnson/angular-drag-and-drop-lists.git#4d32654ab7159689a7767b9be8fc85f9812ca5a8" + "from": "ansible/angular-drag-and-drop-lists#1.4.0", + "resolved": "git://github.com/ansible/angular-drag-and-drop-lists.git#4d32654ab7159689a7767b9be8fc85f9812ca5a8" }, "angular-duration-format": { "version": "1.0.1", @@ -147,8 +147,8 @@ }, "angular-scheduler": { "version": "0.1.0", - "from": "chouseknecht/angular-scheduler#0.1.0", - "resolved": "git://github.com/chouseknecht/angular-scheduler.git#784693054597b9a1c1e49efb4cf94e9054b92e66", + "from": "ansible/angular-scheduler#0.1.0", + "resolved": "git://github.com/ansible/angular-scheduler.git#784693054597b9a1c1e49efb4cf94e9054b92e66", "dependencies": { "angular-tz-extensions": { "version": "0.3.11", @@ -171,13 +171,18 @@ "version": "3.8.0", "from": "lodash@>=3.8.0 <3.9.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.8.0.tgz" + }, + "timezone-js": { + "version": "0.4.14", + "from": "leigh-johnson/timezone-js#0.4.14", + "resolved": "git://github.com/leigh-johnson/timezone-js.git#6937de14ce0c193961538bb5b3b12b7ef62a358f" } } }, "angular-tz-extensions": { "version": "0.3.11", - "from": "chouseknecht/angular-tz-extensions#0.3.12", - "resolved": "git://github.com/chouseknecht/angular-tz-extensions.git#938577310ff9a343eae1348aa04a3ed1a96d097f", + "from": "ansible/angular-tz-extensions#0.3.13", + "resolved": "git://github.com/ansible/angular-tz-extensions.git#33caaa9ccf5dfe29a95962c17c3c9e6b9775be35", "dependencies": { "angular": { "version": "1.4.7", @@ -188,6 +193,11 @@ "version": "3.1.1", "from": "jquery@>=3.1.0 <4.0.0", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.1.1.tgz" + }, + "timezone-js": { + "version": "0.4.14", + "from": "ansible/timezone-js#0.4.14", + "resolved": "git://github.com/ansible/timezone-js.git#6937de14ce0c193961538bb5b3b12b7ef62a358f" } } }, @@ -3643,8 +3653,8 @@ }, "ng-toast": { "version": "2.0.0", - "from": "leigh-johnson/ngToast#2.0.1", - "resolved": "git://github.com/leigh-johnson/ngToast.git#fea95bb34d27687e414619b4f72c11735d909f93" + "from": "ansible/ngToast#2.0.1", + "resolved": "git://github.com/ansible/ngToast.git#fea95bb34d27687e414619b4f72c11735d909f93" }, "node-libs-browser": { "version": "0.6.0", @@ -3710,8 +3720,8 @@ }, "nvd3": { "version": "1.7.1", - "from": "leigh-johnson/nvd3#1.7.1", - "resolved": "git://github.com/leigh-johnson/nvd3.git#a28bcd494a1df0677be7cf2ebc0578f44eb21102", + "from": "ansible/nvd3#1.7.1", + "resolved": "git://github.com/ansible/nvd3.git#a28bcd494a1df0677be7cf2ebc0578f44eb21102", "dependencies": { "d3": { "version": "3.3.13", @@ -3783,7 +3793,14 @@ "optimist": { "version": "0.6.1", "from": "optimist@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz" + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.10", + "from": "minimist@>=0.0.1 <0.1.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" + } + } }, "optionator": { "version": "0.8.2", @@ -4745,8 +4762,8 @@ }, "timezone-js": { "version": "0.4.14", - "from": "leigh-johnson/timezone-js#0.4.14", - "resolved": "git://github.com/leigh-johnson/timezone-js.git#6937de14ce0c193961538bb5b3b12b7ef62a358f" + "from": "ansible/timezone-js", + "resolved": "git://github.com/ansible/timezone-js.git#6937de14ce0c193961538bb5b3b12b7ef62a358f" }, "tiny-lr": { "version": "0.2.1", diff --git a/awx/ui/package.json b/awx/ui/package.json index 737eaed35c..9c876a2db3 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -80,18 +80,18 @@ }, "dependencies": { "angular": "~1.4.7", - "angular-breadcrumb": "leigh-johnson/angular-breadcrumb#0.4.1", - "angular-codemirror": "chouseknecht/angular-codemirror#1.0.4", + "angular-breadcrumb": "github:ansible/angular-breadcrumb#0.4.1", + "angular-codemirror": "github:ansible/angular-codemirror#1.0.4", "angular-cookies": "^1.4.3", - "angular-drag-and-drop-lists": "leigh-johnson/angular-drag-and-drop-lists#1.4.0", + "angular-drag-and-drop-lists": "github:ansible/angular-drag-and-drop-lists#1.4.0", "angular-duration-format": "^1.0.1", "angular-gettext": "^2.3.5", "angular-md5": "^0.1.8", "angular-moment": "^0.10.1", "angular-resource": "^1.4.3", "angular-sanitize": "^1.4.3", - "angular-scheduler": "chouseknecht/angular-scheduler#0.1.0", - "angular-tz-extensions": "chouseknecht/angular-tz-extensions#0.3.12", + "angular-scheduler": "github:ansible/angular-scheduler#0.1.0", + "angular-tz-extensions": "github:ansible/angular-tz-extensions#0.3.13", "angular-ui-router": "^1.0.0-beta.3", "bootstrap": "^3.1.1", "bootstrap-datepicker": "^1.4.0", @@ -104,12 +104,14 @@ "js-yaml": "^3.2.7", "legacy-loader": "0.0.2", "lodash": "^3.8.0", - "lr-infinite-scroll": "lorenzofox3/lrInfiniteScroll", + "lr-infinite-scroll": "github:lorenzofox3/lrInfiniteScroll", "moment": "^2.10.2", - "ng-toast": "leigh-johnson/ngToast#2.0.1", - "nvd3": "leigh-johnson/nvd3#1.7.1", + "ng-toast": "github:ansible/ngToast#2.0.1", + "nvd3": "github:ansible/nvd3#1.7.1", "reconnectingwebsocket": "^1.0.0", + "rrule": "github:jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", "select2": "^4.0.2", - "sprintf-js": "^1.0.3" + "sprintf-js": "^1.0.3", + "timezone-js": "github:ansible/timezone-js" } } From ee38be85d42e8427723a64bb86bf186128d1cac7 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 12 Dec 2016 10:54:00 -0500 Subject: [PATCH 03/39] Better error handling on deprovision_node management command --- awx/main/management/commands/deprovision_node.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/awx/main/management/commands/deprovision_node.py b/awx/main/management/commands/deprovision_node.py index 52b9e4f115..251816703e 100644 --- a/awx/main/management/commands/deprovision_node.py +++ b/awx/main/management/commands/deprovision_node.py @@ -1,7 +1,7 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError from optparse import make_option from awx.main.models import Instance @@ -16,8 +16,9 @@ class Command(BaseCommand): help='Hostname used during provisioning'), ) - def handle(self, **options): - # Get the instance. + def handle(self, *args, **options): + if not options.get('name'): + raise CommandError("--name is a required argument") instance = Instance.objects.filter(hostname=options.get('name')) if instance.exists(): instance.delete() From 7d23160c1add4cbde6d58225a737a4145af220f3 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Thu, 23 Feb 2017 15:53:33 -0500 Subject: [PATCH 04/39] Modularized what was left in the controllers directory --- awx/ui/client/src/app.js | 187 +---- awx/ui/client/src/controllers/Credentials.js | 631 --------------- awx/ui/client/src/controllers/Home.js | 142 ---- awx/ui/client/src/controllers/JobEvents.js | 356 --------- awx/ui/client/src/controllers/JobHosts.js | 121 --- awx/ui/client/src/controllers/Projects.js | 749 ------------------ awx/ui/client/src/controllers/Schedules.js | 86 -- awx/ui/client/src/controllers/Sockets.js | 119 --- awx/ui/client/src/controllers/Teams.js | 247 ------ awx/ui/client/src/controllers/Users.js | 357 --------- .../add/credentials-add.controller.js | 177 +++++ .../edit/credentials-edit.controller.js | 346 ++++++++ .../list/credentials-list.controller.js | 106 +++ awx/ui/client/src/credentials/main.js | 40 +- .../counts/dashboard-counts.block.less | 2 +- .../counts/dashboard-counts.directive.js | 2 +- .../counts/dashboard-counts.partial.html | 0 .../src/{ => home}/dashboard/counts/main.js | 0 .../{ => home}/dashboard/dashboard.block.less | 2 +- .../dashboard/dashboard.directive.js | 2 +- .../dashboard/dashboard.partial.html | 0 .../graphs/dashboard-graphs.block.less | 2 +- .../graphs/dashboard-graphs.directive.js | 2 +- .../graphs/dashboard-graphs.partial.html | 0 .../adjust-graph-size.service.js | 0 .../graph-helpers/auto-size.directive.js | 0 .../dashboard/graphs/graph-helpers/main.js | 0 .../job-status/job-status-graph.directive.js | 2 +- .../job-status/job-status-graph.service.js | 0 .../job-status/job_status_graph.partial.html | 0 .../dashboard/graphs/job-status/main.js | 2 +- .../src/{ => home}/dashboard/graphs/main.js | 0 .../hosts/dashboard-hosts-edit.controller.js | 0 .../hosts/dashboard-hosts-list.controller.js | 0 .../dashboard/hosts/dashboard-hosts.form.js | 0 .../dashboard/hosts/dashboard-hosts.list.js | 0 .../hosts/dashboard-hosts.service.js | 0 .../src/{ => home}/dashboard/hosts/main.js | 2 +- .../dashboard/lists/dashboard-list.block.less | 2 +- .../job-templates-list.directive.js | 2 +- .../job-templates-list.partial.html | 0 .../dashboard/lists/job-templates/main.js | 4 +- .../lists/jobs/jobs-list.directive.js | 2 +- .../lists/jobs/jobs-list.partial.html | 0 .../{ => home}/dashboard/lists/jobs/main.js | 0 .../src/{ => home}/dashboard/lists/main.js | 0 .../client/src/{ => home}/dashboard/main.js | 0 awx/ui/client/src/home/home.controller.js | 125 +++ .../home.html => home/home.partial.html} | 1 - awx/ui/client/src/home/home.route.js | 46 ++ awx/ui/client/src/home/main.js | 12 + .../projects/add/projects-add.controller.js | 154 ++++ .../projects/edit/projects-edit.controller.js | 297 +++++++ .../projects/list/projects-list.controller.js | 299 +++++++ awx/ui/client/src/projects/main.js | 51 ++ .../src/teams/add/teams-add.controller.js | 68 ++ .../src/teams/edit/teams-edit.controller.js | 97 +++ .../src/teams/list/teams-list.controller.js | 81 ++ awx/ui/client/src/teams/main.js | 47 ++ .../src/users/add/users-add.controller.js | 119 +++ .../src/users/edit/users-edit.controller.js | 179 +++++ .../src/users/list/users-list.controller.js | 90 +++ awx/ui/client/src/users/main.js | 47 ++ 63 files changed, 2404 insertions(+), 3001 deletions(-) delete mode 100644 awx/ui/client/src/controllers/Credentials.js delete mode 100644 awx/ui/client/src/controllers/Home.js delete mode 100644 awx/ui/client/src/controllers/JobEvents.js delete mode 100644 awx/ui/client/src/controllers/JobHosts.js delete mode 100644 awx/ui/client/src/controllers/Projects.js delete mode 100644 awx/ui/client/src/controllers/Schedules.js delete mode 100644 awx/ui/client/src/controllers/Sockets.js delete mode 100644 awx/ui/client/src/controllers/Teams.js delete mode 100644 awx/ui/client/src/controllers/Users.js create mode 100644 awx/ui/client/src/credentials/add/credentials-add.controller.js create mode 100644 awx/ui/client/src/credentials/edit/credentials-edit.controller.js create mode 100644 awx/ui/client/src/credentials/list/credentials-list.controller.js rename awx/ui/client/src/{ => home}/dashboard/counts/dashboard-counts.block.less (97%) rename awx/ui/client/src/{ => home}/dashboard/counts/dashboard-counts.directive.js (97%) rename awx/ui/client/src/{ => home}/dashboard/counts/dashboard-counts.partial.html (100%) rename awx/ui/client/src/{ => home}/dashboard/counts/main.js (100%) rename awx/ui/client/src/{ => home}/dashboard/dashboard.block.less (93%) rename awx/ui/client/src/{ => home}/dashboard/dashboard.directive.js (80%) rename awx/ui/client/src/{ => home}/dashboard/dashboard.partial.html (100%) rename awx/ui/client/src/{ => home}/dashboard/graphs/dashboard-graphs.block.less (98%) rename awx/ui/client/src/{ => home}/dashboard/graphs/dashboard-graphs.directive.js (86%) rename awx/ui/client/src/{ => home}/dashboard/graphs/dashboard-graphs.partial.html (100%) rename awx/ui/client/src/{ => home}/dashboard/graphs/graph-helpers/adjust-graph-size.service.js (100%) rename awx/ui/client/src/{ => home}/dashboard/graphs/graph-helpers/auto-size.directive.js (100%) rename awx/ui/client/src/{ => home}/dashboard/graphs/graph-helpers/main.js (100%) rename awx/ui/client/src/{ => home}/dashboard/graphs/job-status/job-status-graph.directive.js (98%) rename awx/ui/client/src/{ => home}/dashboard/graphs/job-status/job-status-graph.service.js (100%) rename awx/ui/client/src/{ => home}/dashboard/graphs/job-status/job_status_graph.partial.html (100%) rename awx/ui/client/src/{ => home}/dashboard/graphs/job-status/main.js (86%) rename awx/ui/client/src/{ => home}/dashboard/graphs/main.js (100%) rename awx/ui/client/src/{ => home}/dashboard/hosts/dashboard-hosts-edit.controller.js (100%) rename awx/ui/client/src/{ => home}/dashboard/hosts/dashboard-hosts-list.controller.js (100%) rename awx/ui/client/src/{ => home}/dashboard/hosts/dashboard-hosts.form.js (100%) rename awx/ui/client/src/{ => home}/dashboard/hosts/dashboard-hosts.list.js (100%) rename awx/ui/client/src/{ => home}/dashboard/hosts/dashboard-hosts.service.js (100%) rename awx/ui/client/src/{ => home}/dashboard/hosts/main.js (98%) rename awx/ui/client/src/{ => home}/dashboard/lists/dashboard-list.block.less (98%) rename awx/ui/client/src/{ => home}/dashboard/lists/job-templates/job-templates-list.directive.js (97%) rename awx/ui/client/src/{ => home}/dashboard/lists/job-templates/job-templates-list.partial.html (100%) rename awx/ui/client/src/{ => home}/dashboard/lists/job-templates/main.js (64%) rename awx/ui/client/src/{ => home}/dashboard/lists/jobs/jobs-list.directive.js (94%) rename awx/ui/client/src/{ => home}/dashboard/lists/jobs/jobs-list.partial.html (100%) rename awx/ui/client/src/{ => home}/dashboard/lists/jobs/main.js (100%) rename awx/ui/client/src/{ => home}/dashboard/lists/main.js (100%) rename awx/ui/client/src/{ => home}/dashboard/main.js (100%) create mode 100644 awx/ui/client/src/home/home.controller.js rename awx/ui/client/src/{partials/home.html => home/home.partial.html} (99%) create mode 100644 awx/ui/client/src/home/home.route.js create mode 100644 awx/ui/client/src/home/main.js create mode 100644 awx/ui/client/src/projects/add/projects-add.controller.js create mode 100644 awx/ui/client/src/projects/edit/projects-edit.controller.js create mode 100644 awx/ui/client/src/projects/list/projects-list.controller.js create mode 100644 awx/ui/client/src/projects/main.js create mode 100644 awx/ui/client/src/teams/add/teams-add.controller.js create mode 100644 awx/ui/client/src/teams/edit/teams-edit.controller.js create mode 100644 awx/ui/client/src/teams/list/teams-list.controller.js create mode 100644 awx/ui/client/src/teams/main.js create mode 100644 awx/ui/client/src/users/add/users-add.controller.js create mode 100644 awx/ui/client/src/users/edit/users-edit.controller.js create mode 100644 awx/ui/client/src/users/list/users-list.controller.js create mode 100644 awx/ui/client/src/users/main.js 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: '
' + i18n._('Are you sure you want to delete the credential below?') + '
' + $filter('sanitize')(name) + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; -} - -CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log', - '$stateParams', 'Rest', 'Alert', 'CredentialList', 'Prompt', 'ClearScope', - 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n' -]; - - -export function CredentialsAdd($scope, $rootScope, $compile, $location, $log, - $stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors, - ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange, - OwnerChange, FormSave, $state, CreateSelect2, i18n) { - ClearScope(); - - // Inject dynamic view - var form = CredentialForm, - defaultUrl = GetBasePath('credentials'), - url; - - init(); - - function init() { - // Load the list of options for Kind - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'kind', - variable: 'credential_kind_options' - }); - - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'become_method', - variable: 'become_options' - }); - - CreateSelect2({ - element: '#credential_become_method', - multiple: false - }); - - CreateSelect2({ - element: '#credential_kind', - multiple: false - }); - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - $scope.keyEntered = false; - $scope.permissionsTooltip = i18n._('Please save before assigning permissions'); - - // determine if the currently logged-in user may share this credential - // previous commentary said: "$rootScope.current_user isn't available because a call to the config endpoint hasn't finished resolving yet" - // I'm 99% sure this state's will never resolve block will be rejected if setup surrounding config endpoint hasn't completed - if ($rootScope.current_user && $rootScope.current_user.is_superuser) { - $scope.canShareCredential = true; - } else { - Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`); - Rest.get() - .success(function(data) { - $scope.canShareCredential = (data.count) ? true : false; - }).error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status }); - }); - } - } - - if (!Empty($stateParams.user_id)) { - // Get the username based on incoming route - $scope.owner = 'user'; - $scope.user = $stateParams.user_id; - OwnerChange({ scope: $scope }); - url = GetBasePath('users') + $stateParams.user_id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - $scope.user_username = data.username; - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user. GET status: ' + status }); - }); - } else if (!Empty($stateParams.team_id)) { - // Get the username based on incoming route - $scope.owner = 'team'; - $scope.team = $stateParams.team_id; - OwnerChange({ scope: $scope }); - url = GetBasePath('teams') + $stateParams.team_id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - $scope.team_name = data.name; - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve team. GET status: ' + status }); - }); - } else { - // default type of owner to a user - $scope.owner = 'user'; - OwnerChange({ scope: $scope }); - } - - $scope.$watch("ssh_key_data", function(val) { - if (val === "" || val === null || val === undefined) { - $scope.keyEntered = false; - $scope.ssh_key_unlock_ask = false; - $scope.ssh_key_unlock = ""; - } else { - $scope.keyEntered = true; - } - }); - - // Handle Kind change - $scope.kindChange = function() { - KindChange({ scope: $scope, form: form, reset: true }); - }; - - $scope.becomeMethodChange = function() { - BecomeMethodChange({ scope: $scope }); - }; - - // Save - $scope.formSave = function() { - if ($scope[form.name + '_form'].$valid) { - FormSave({ scope: $scope, mode: 'add' }); - } - }; - - $scope.formCancel = function() { - $state.go('credentials'); - }; - - // Password change - $scope.clearPWConfirm = function(fld) { - // If password value changes, make sure password_confirm must be re-entered - $scope[fld] = ''; - $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); - }; - - // Respond to 'Ask at runtime?' checkbox - $scope.ask = function(fld, associated) { - if ($scope[fld + '_ask']) { - $scope[fld] = 'ASK'; - $("#" + form.name + "_" + fld + "_input").attr("type", "text"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); - if (associated !== "undefined") { - $("#" + form.name + "_" + fld + "_input").attr("type", "password"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); - } - } else { - $scope[fld] = ''; - $("#" + form.name + "_" + fld + "_input").attr("type", "password"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); - if (associated !== "undefined") { - $("#" + form.name + "_" + fld + "_input").attr("type", "text"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); - } - } - }; - - // Click clear button - $scope.clear = function(fld, associated) { - $scope[fld] = ''; - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); - $scope[form.name + '_form'].$setDirty(); - }; - -} - -CredentialsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', - '$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert', - 'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange', - 'OwnerChange', 'FormSave', '$state', 'CreateSelect2', 'i18n' -]; - -export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, - $stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt, - GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, FormSave, Wait, - $state, CreateSelect2, Authorization, i18n) { - - ClearScope(); - - var defaultUrl = GetBasePath('credentials'), - form = CredentialForm, - base = $location.path().replace(/^\//, '').split('/')[0], - master = {}, - id = $stateParams.credential_id; - - init(); - - function init() { - $scope.id = id; - $scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - $scope.canShareCredential = false; - Wait('start'); - if (!$rootScope.current_user) { - Authorization.restoreUserInfo(); - } - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'kind', - variable: 'credential_kind_options', - callback: 'choicesReadyCredential' - }); - - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'become_method', - variable: 'become_options' - }); - - if ($rootScope.current_user && $rootScope.current_user.is_superuser) { - $scope.canShareCredential = true; - } else { - Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`); - Rest.get() - .success(function(data) { - $scope.canShareCredential = (data.count) ? true : false; - Wait('stop'); - }).error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status }); - }); - } - - $scope.$watch('organization', function(val) { - if (val === undefined) { - $scope.permissionsTooltip = i18n._('Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.'); - } else { - $scope.permissionsTooltip = ''; - } - }); - - setAskCheckboxes(); - OwnerChange({ scope: $scope }); - $scope.$watch("ssh_key_data", function(val) { - if (val === "" || val === null || val === undefined) { - $scope.keyEntered = false; - $scope.ssh_key_unlock_ask = false; - $scope.ssh_key_unlock = ""; - } else { - $scope.keyEntered = true; - } - }); - } - - function setAskCheckboxes() { - var fld, i; - for (fld in form.fields) { - if (form.fields[fld].type === 'sensitive' && $scope[fld] === 'ASK') { - // turn on 'ask' checkbox for password fields with value of 'ASK' - $("#" + form.name + "_" + fld + "_input").attr("type", "text"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); - $("#" + fld + "-clear-btn").attr("disabled", "disabled"); - $scope[fld + '_ask'] = true; - } else { - $scope[fld + '_ask'] = false; - $("#" + fld + "-clear-btn").removeAttr("disabled"); - } - master[fld + '_ask'] = $scope[fld + '_ask']; - } - - // Set kind field to the correct option - for (i = 0; i < $scope.credential_kind_options.length; i++) { - if ($scope.kind === $scope.credential_kind_options[i].value) { - $scope.kind = $scope.credential_kind_options[i]; - break; - } - } - } - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReadyCredential', function() { - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl + ':id/'); - Rest.get({ params: { id: id } }) - .success(function(data) { - if (data && data.summary_fields && - data.summary_fields.organization && - data.summary_fields.organization.id) { - $scope.needsRoleList = true; - } else { - $scope.needsRoleList = false; - } - - $scope.credential_name = data.name; - - var i, fld; - - - for (fld in form.fields) { - if (data[fld] !== null && data[fld] !== undefined) { - $scope[fld] = data[fld]; - master[fld] = $scope[fld]; - } - if (form.fields[fld].type === 'lookup' && 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] = - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; - } - } - - if (!Empty($scope.user)) { - $scope.owner = 'user'; - } else { - $scope.owner = 'team'; - } - master.owner = $scope.owner; - - for (i = 0; i < $scope.become_options.length; i++) { - if ($scope.become_options[i].value === data.become_method) { - $scope.become_method = $scope.become_options[i]; - break; - } - } - - if ($scope.become_method && $scope.become_method.value === "") { - $scope.become_method = null; - } - master.become_method = $scope.become_method; - - $scope.$watch('become_method', function(val) { - if (val !== null) { - if (val.value === "") { - $scope.become_username = ""; - $scope.become_password = ""; - } - } - }); - - for (i = 0; i < $scope.credential_kind_options.length; i++) { - if ($scope.credential_kind_options[i].value === data.kind) { - $scope.kind = $scope.credential_kind_options[i]; - break; - } - } - - KindChange({ - scope: $scope, - form: form, - reset: false - }); - - master.kind = $scope.kind; - - CreateSelect2({ - element: '#credential_become_method', - multiple: false - }); - - CreateSelect2({ - element: '#credential_kind', - multiple: false - }); - - switch (data.kind) { - case 'aws': - $scope.access_key = data.username; - $scope.secret_key = data.password; - master.access_key = $scope.access_key; - master.secret_key = $scope.secret_key; - break; - case 'ssh': - $scope.ssh_password = data.password; - master.ssh_password = $scope.ssh_password; - break; - case 'rax': - $scope.api_key = data.password; - master.api_key = $scope.api_key; - break; - case 'gce': - $scope.email_address = data.username; - $scope.project = data.project; - break; - case 'azure': - $scope.subscription = data.username; - break; - } - $scope.credential_obj = data; - - $scope.$emit('credentialLoaded'); - Wait('stop'); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to retrieve Credential: ' + $stateParams.id + '. GET status: ' + status - }); - }); - }); - - // Save changes to the parent - $scope.formSave = function() { - if ($scope[form.name + '_form'].$valid) { - FormSave({ scope: $scope, mode: 'edit' }); - } - }; - - // Handle Owner change - $scope.ownerChange = function() { - OwnerChange({ scope: $scope }); - }; - - // Handle Kind change - $scope.kindChange = function() { - KindChange({ scope: $scope, form: form, reset: true }); - }; - - $scope.becomeMethodChange = function() { - BecomeMethodChange({ scope: $scope }); - }; - - $scope.formCancel = function() { - $state.transitionTo('credentials'); - }; - - // Related set: Add button - $scope.add = function(set) { - $rootScope.flashMessage = null; - $location.path('/' + base + '/' + $stateParams.id + '/' + set + '/add'); - }; - - // Related set: Edit button - $scope.edit = function(set, id) { - $rootScope.flashMessage = null; - $location.path('/' + base + '/' + $stateParams.id + '/' + set + '/' + id); - }; - - // Related set: Delete button - $scope['delete'] = function(set, itm_id, name, title) { - $rootScope.flashMessage = null; - - var action = function() { - var url = defaultUrl + id + '/' + set + '/'; - 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: 'Error!', - msg: 'Call to ' + url + ' failed. POST returned status: ' + status - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - body: '
' + i18n.sprintf(i18n._('Are you sure you want to remove the %s below from %s?'), title, $scope.name) + '
' + name + '
', - action: action, - actionText: i18n._('DELETE') - }); - - }; - - // Password change - $scope.clearPWConfirm = function(fld) { - // If password value changes, make sure password_confirm must be re-entered - $scope[fld] = ''; - $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); - }; - - // Respond to 'Ask at runtime?' checkbox - $scope.ask = function(fld, associated) { - if ($scope[fld + '_ask']) { - $scope[fld] = 'ASK'; - $("#" + form.name + "_" + fld + "_input").attr("type", "text"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); - if (associated !== "undefined") { - $("#" + form.name + "_" + fld + "_input").attr("type", "password"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); - } - } else { - $scope[fld] = ''; - $("#" + form.name + "_" + fld + "_input").attr("type", "password"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); - if (associated !== "undefined") { - $("#" + form.name + "_" + fld + "_input").attr("type", "text"); - $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); - } - } - }; - - $scope.clear = function(fld, associated) { - $scope[fld] = ''; - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); - $scope[form.name + '_form'].$setDirty(); - }; - -} - -CredentialsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', - '$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert', - 'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices', - 'KindChange', 'BecomeMethodChange', 'Empty', 'OwnerChange', - 'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n', -]; diff --git a/awx/ui/client/src/controllers/Home.js b/awx/ui/client/src/controllers/Home.js deleted file mode 100644 index 3c81e7a798..0000000000 --- a/awx/ui/client/src/controllers/Home.js +++ /dev/null @@ -1,142 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Home - * @description This controller's for the dashboard -*/ - - -/** - * @ngdoc method - * @name controllers.function:Home#Home - * @methodOf controllers.function:Home - * @description this function loads all the widgets on the dashboard. - * dashboardReady (emit) - this is called when the preliminary parts of the dashboard have been loaded, and loads each of the widgets. Note that the - * Host count graph should only be loaded if the user is a super user - * -*/ - -export function Home($scope, $compile, $stateParams, $rootScope, $location, $log, Wait, - ClearScope, Rest, GetBasePath, ProcessErrors, $window, graphData){ - - ClearScope('home'); - - var dataCount = 0; - - $scope.$on('ws-jobs', function () { - Rest.setUrl(GetBasePath('dashboard')); - Rest.get() - .success(function (data) { - $scope.dashboardData = data; - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard host graph data: ' + status }); - }); - - Rest.setUrl(GetBasePath("jobs") + "?order_by=-finished&page_size=5&finished__isnull=false"); - Rest.get() - .success(function (data) { - $scope.dashboardJobsListData = data.results; - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); - }); - - Rest.setUrl(GetBasePath("unified_job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false&type=workflow_job_template,job_template"); - Rest.get() - .success(function (data) { - $scope.dashboardJobTemplatesListData = data.results; - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); - }); - - }); - - if ($scope.removeDashboardDataLoadComplete) { - $scope.removeDashboardDataLoadComplete(); - } - $scope.removeDashboardDataLoadComplete = $scope.$on('dashboardDataLoadComplete', function () { - dataCount++; - if (dataCount === 3) { - Wait("stop"); - dataCount = 0; - } - }); - - if ($scope.removeDashboardReady) { - $scope.removeDashboardReady(); - } - $scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) { - $scope.dashboardCountsData = data; - $scope.graphData = graphData; - $scope.$emit('dashboardDataLoadComplete'); - - var cleanupJobListener = - $rootScope.$on('DataReceived:JobStatusGraph', function(e, data) { - $scope.graphData.jobStatus = data; - }); - - $scope.$on('$destroy', function() { - cleanupJobListener(); - }); - }); - - if ($scope.removeDashboardJobsListReady) { - $scope.removeDashboardJobsListReady(); - } - $scope.removeDashboardJobsListReady = $scope.$on('dashboardJobsListReady', function (e, data) { - $scope.dashboardJobsListData = data; - $scope.$emit('dashboardDataLoadComplete'); - }); - - if ($scope.removeDashboardJobTemplatesListReady) { - $scope.removeDashboardJobTemplatesListReady(); - } - $scope.removeDashboardJobTemplatesListReady = $scope.$on('dashboardJobTemplatesListReady', function (e, data) { - $scope.dashboardJobTemplatesListData = data; - $scope.$emit('dashboardDataLoadComplete'); - }); - - $scope.refresh = function () { - Wait('start'); - Rest.setUrl(GetBasePath('dashboard')); - Rest.get() - .success(function (data) { - $scope.dashboardData = data; - $scope.$emit('dashboardReady', data); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); - }); - Rest.setUrl(GetBasePath("jobs") + "?order_by=-finished&page_size=5&finished__isnull=false"); - Rest.get() - .success(function (data) { - data = data.results; - $scope.$emit('dashboardJobsListReady', data); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); - }); - Rest.setUrl(GetBasePath("unified_job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false&type=workflow_job_template,job_template"); - Rest.get() - .success(function (data) { - data = data.results; - $scope.$emit('dashboardJobTemplatesListReady', data); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard job templates list: ' + status }); - }); - }; - - $scope.refresh(); -} - -Home.$inject = ['$scope', '$compile', '$stateParams', '$rootScope', '$location', '$log','Wait', - 'ClearScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$window', 'graphData' -]; diff --git a/awx/ui/client/src/controllers/JobEvents.js b/awx/ui/client/src/controllers/JobEvents.js deleted file mode 100644 index 0ea23a76ce..0000000000 --- a/awx/ui/client/src/controllers/JobEvents.js +++ /dev/null @@ -1,356 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:JobEvent - * @description This controller's for the job event page -*/ - - -export function JobEventsList($sce, $filter, $scope, $rootScope, $location, $log, $stateParams, Rest, Alert, JobEventList, GenerateList, - Prompt, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren, - FormatDate, EventView, Wait) { - - ClearScope(); - - var list = JobEventList, - generator = GenerateList; - - // @issue: OLD SEARCH - // var defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_events/', //?parent__isnull=1'; - // page; - - list.base = $location.path(); - $scope.job_id = $stateParams.id; - $rootScope.flashMessage = null; - $scope.selected = []; - $scope.expand = true; //on load, automatically expand all nodes - - $scope.parentNode = 'parent-event'; // used in ngClass to dynamically set row level class and control - $scope.childNode = 'child-event'; // link color and cursor - - if ($scope.removeSetHostLinks) { - $scope.removeSetHostLinks(); - } - $scope.removeSetHostLinks = $scope.$on('SetHostLinks', function (e, inventory_id) { - for (var i = 0; i < $scope.jobevents.length; i++) { - if ($scope.jobevents[i].summary_fields.host) { - $scope.jobevents[i].hostLink = "/#/inventories/" + inventory_id; - //encodeURI($scope.jobevents[i].summary_fields.host.name); - } - } - }); - - function formatJSON(eventData) { - //turn JSON event data into an html form - - var i, n, rows, fld, txt, - html = '', - found = false; - - if (eventData.res) { - if (typeof eventData.res === 'string') { - n = eventData.res.match(/\n/g); - rows = (n) ? n.length : 1; - rows = (rows > 10) ? 10 : rows; - found = true; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "
\n"; - } else { - for (fld in eventData.res) { - if ((fld === 'msg' || fld === 'stdout' || fld === 'stderr') && - (eventData.res[fld] !== null && eventData.res[fld] !== '')) { - html += "
\n"; - html += "\n"; - n = eventData.res[fld].match(/\n/g); - rows = (n) ? n.length : 1; - rows = (rows > 10) ? 10 : rows; - html += "\n"; - //html += "
" + eventData.res[fld] + "
\n"; - html += "
\n"; - found = true; - } - if (fld === "results" && Array.isArray(eventData.res[fld]) && eventData.res[fld].length > 0) { - txt = ''; - for (i = 0; i < eventData.res[fld].length; i++) { - txt += eventData.res[fld][i]; - } - n = txt.match(/\n/g); - rows = (n) ? n.length : 1; - rows = (rows > 10) ? 10 : rows; - if (txt !== '') { - html += "
\n"; - html += "\n"; - html += "\n"; - //html += "
" + txt + "
\n"; - html += "
\n"; - found = true; - } - } - if (fld === "rc" && eventData.res[fld] !== '') { - - html += "
\n"; - html += "
" + eventData.res[fld] + "
\n"; - //html += "\n"; - html += "
\n"; - found = true; - } - } - } - html = (found) ? "
\n" + html + "
\n" : ''; - } - if (eventData.hosts) { - html = "" + eventData.host + "\n" + html; - } else { - html = (html === '') ? null : html; - } - return html; - } - - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - $scope.removePostRefresh = $scope.$on('PostRefresh', function () { - // Initialize the parent levels - - generator.inject(list, { mode: 'edit', scope: $scope }); - - var set = $scope[list.name], i; - for (i = 0; i < set.length; i++) { - set[i].event_display = set[i].event_display.replace(/^\u00a0*/g, ''); - if (set[i].event_level < 3) { - set[i].ngicon = 'fa fa-minus-square-o node-toggle'; - set[i]['class'] = 'parentNode'; - } else { - set[i].ngicon = 'fa fa-square-o node-no-toggle'; - set[i]['class'] = 'childNode'; - set[i].event_detail = $sce.trustAsHtml(formatJSON(set[i].event_data)); - } - set[i].show = true; - set[i].spaces = set[i].event_level * 24; - if ($scope.jobevents[i].failed) { - $scope.jobevents[i].status = 'error'; - if (i === set.length - 1) { - $scope.jobevents[i].statusBadgeToolTip = "A failure occurred durring one or more playbook tasks."; - } else if (set[i].event_level < 3) { - $scope.jobevents[i].statusBadgeToolTip = "A failure occurred within the children of this event."; - } else { - $scope.jobevents[i].statusBadgeToolTip = "A failure occurred. Click to view details"; - } - } else if ($scope.jobevents[i].changed) { - $scope.jobevents[i].status = 'changed'; - if (i === set.length - 1) { - $scope.jobevents[i].statusBadgeToolTip = "A change was completed durring one or more playbook tasks."; - } else if (set[i].event_level < 3) { - $scope.jobevents[i].statusBadgeToolTip = "A change was completed by one or more children of this event."; - } else { - $scope.jobevents[i].statusBadgeToolTip = "A change was completed. Click to view details"; - } - } else { - $scope.jobevents[i].status = 'success'; - if (i === set.length - 1) { - $scope.jobevents[i].statusBadgeToolTip = "All playbook tasks completed successfully."; - } else if (set[i].event_level < 3) { - $scope.jobevents[i].statusBadgeToolTip = "All the children of this event completed successfully."; - } else { - $scope.jobevents[i].statusBadgeToolTip = "No errors occurred. Click to view details"; - } - } - //cDate = new Date(set[i].created); - //set[i].created = FormatDate(cDate); - set[i].created = $filter('longDate')(set[i].created); - } - - // Need below lookup to get inventory_id, which is not on event record. Plus, good idea to get status and name - // from job in the event that there are no job event records - Rest.setUrl(GetBasePath('jobs') + $scope.job_id); - Rest.get() - .success(function (data) { - $scope.job_status = data.status; - $scope.job_name = data.summary_fields.job_template.name; - $scope.$emit('SetHostLinks', data.inventory); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job status for job: ' + $scope.job_id + '. GET status: ' + status - }); - }); - }); - - // @issue: OLD SEARCH - // SearchInit({ - // scope: $scope, - // set: 'jobevents', - // list: list, - // url: defaultUrl - // }); - // - // page = ($stateParams.page) ? parseInt($stateParams.page,10) - 1 : null; - // - // PaginateInit({ - // scope: $scope, - // list: list, - // url: defaultUrl, - // page: page - // }); - // - // // Called from Inventories tab, host failed events link: - // if ($stateParams.host) { - // $scope[list.iterator + 'SearchField'] = 'host'; - // $scope[list.iterator + 'SearchValue'] = $stateParams.host; - // $scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label; - // } - // - // $scope.search(list.iterator, $stateParams.page); - - $scope.toggle = function (id) { - ToggleChildren({ - scope: $scope, - list: list, - id: id - }); - }; - - $scope.viewJobEvent = function (id) { - EventView({ - event_id: id - }); - }; - - $scope.refresh = function () { - // @issue: OLD SEARCH - // $scope.jobSearchSpin = true; - $scope.jobLoading = true; - Wait('start'); - - // @issue: OLD SEARCH - // Refresh({ - // scope: $scope, - // set: 'jobevents', - // iterator: 'jobevent', - // url: $scope.current_url - // }); - }; -} - -JobEventsList.$inject = ['$sce', '$filter', '$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'JobEventList', - 'generateList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', - 'GetBasePath', 'LookUpInit', 'ToggleChildren', 'FormatDate', 'EventView', 'Wait' -]; - -export function JobEventsEdit($scope, $rootScope, $compile, $location, $log, $stateParams, JobEventsForm, GenerateForm, - Rest, Alert, ProcessErrors, ClearScope, GetBasePath, FormatDate, EventView, Wait) { - - ClearScope(); - - var form = JobEventsForm, - generator = GenerateForm, - defaultUrl = GetBasePath('base') + 'job_events/' + $stateParams.event_id + '/'; - - generator.inject(form, { mode: 'edit', related: true, scope: $scope}); - generator.reset(); - - // Retrieve detail record and prepopulate the form - Wait('start'); - Rest.setUrl(defaultUrl); - Rest.get() - .success(function (data) { - var cDate, fld, n, rows; - $scope.event_display = data.event_display.replace(/^\u00a0*/g, ''); - for (fld in form.fields) { - switch (fld) { - case 'status': - if (data.failed) { - $scope.status = 'error'; - } else if (data.changed) { - $scope.status = 'changed'; - } else { - $scope.status = 'success'; - } - break; - case 'created': - cDate = new Date(data.created); - $scope.created = FormatDate(cDate); - break; - case 'host': - if (data.summary_fields && data.summary_fields.host) { - $scope.host = data.summary_fields.host.name; - } - break; - case 'id': - case 'task': - case 'play': - $scope[fld] = data[fld]; - break; - case 'start': - case 'end': - if (data.event_data && data.event_data.res && data.event_data.res[fld] !== undefined) { - cDate = new Date(data.event_data.res[fld]); - $scope[fld] = FormatDate(cDate); - } - break; - case 'msg': - case 'stdout': - case 'stderr': - case 'delta': - case 'rc': - if (data.event_data && data.event_data.res && data.event_data.res[fld] !== undefined) { - $scope[fld] = data.event_data.res[fld]; - if (form.fields[fld].type === 'textarea') { - n = data.event_data.res[fld].match(/\n/g); - rows = (n) ? n.length : 1; - rows = (rows > 15) ? 5 : rows; - $('textarea[name="' + fld + '"]').attr('rows', rows); - } - } - break; - case 'module_name': - case 'module_args': - if (data.event_data.res && data.event_data.res.invocation) { - $scope[fld] = data.event_data.res.invocation.fld; - } - break; - } - } - Wait('stop'); - }) - .error(function (data) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve host: ' + $stateParams.event_id + - '. GET status: ' + status }); - }); - - $scope.navigateBack = function () { - var url = '/jobs/' + $stateParams.job_id + '/job_events'; - if ($stateParams.page) { - url += '?page=' + $stateParams.page; - } - $location.url(url); - }; - - $scope.rawView = function () { - EventView({ - "event_id": $scope.id - }); - }; - -} - -JobEventsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$stateParams', 'JobEventsForm', 'GenerateForm', - 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'FormatDate', 'EventView', 'Wait' -]; diff --git a/awx/ui/client/src/controllers/JobHosts.js b/awx/ui/client/src/controllers/JobHosts.js deleted file mode 100644 index e078bc4df7..0000000000 --- a/awx/ui/client/src/controllers/JobHosts.js +++ /dev/null @@ -1,121 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:JobHosts - * @description This controller's for the job hosts page -*/ - - -export function JobHostSummaryList($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, JobHostList, GenerateList, - Prompt, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, - JobStatusToolTip) { - - ClearScope(); - - var list = JobHostList, - // @issue: OLD SEARCH - // defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_host_summaries/', - view = GenerateList, - inventory; - - $scope.job_id = $stateParams.id; - $scope.host_id = null; - - // After a refresh, populate any needed summary field values on each row - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - $scope.removePostRefresh = $scope.$on('PostRefresh', function () { - - // Set status, tooltips, badges icons, etc. - $scope.jobhosts.forEach(function(element, i) { - $scope.jobhosts[i].host_name = ($scope.jobhosts[i].summary_fields.host) ? $scope.jobhosts[i].summary_fields.host.name : ''; - $scope.jobhosts[i].status = ($scope.jobhosts[i].failed) ? 'failed' : 'success'; - $scope.jobhosts[i].statusBadgeToolTip = JobStatusToolTip($scope.jobhosts[i].status) + - " Click to view details."; - if ($scope.jobhosts[i].summary_fields.host) { - $scope.jobhosts[i].statusLinkTo = '/#/job_events/' + $scope.jobhosts[i].job + '/?host=' + - encodeURI($scope.jobhosts[i].summary_fields.host.name); - } - else { - $scope.jobhosts[i].statusLinkTo = '/#/job_events/' + $scope.jobhosts[i].job; - } - }); - - for (var i = 0; i < $scope.jobhosts.length; i++) { - $scope.jobhosts[i].hostLinkTo = '/#/inventories/' + inventory + '/?host_name=' + - encodeURI($scope.jobhosts[i].summary_fields.host.name); - } - }); - - if ($scope.removeJobReady) { - $scope.removeJobReady(); - } - $scope.removeJobReady = $scope.$on('JobReady', function() { - view.inject(list, { mode: 'edit', scope: $scope }); - - // @issue: OLD SEARCH - // SearchInit({ - // scope: $scope, - // set: 'jobhosts', - // list: list, - // url: defaultUrl - // }); - // - // PaginateInit({ - // scope: $scope, - // list: list, - // url: defaultUrl - // }); - // - // // Called from Inventories tab, host failed events link: - // if ($stateParams.host_name) { - // $scope[list.iterator + 'SearchField'] = 'host'; - // $scope[list.iterator + 'SearchValue'] = $stateParams.host_name; - // $scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label; - // } - // $scope.search(list.iterator); - }); - - Rest.setUrl(GetBasePath('jobs') + $scope.job_id); - Rest.get() - .success(function (data) { - inventory = data.inventory; - $scope.job_status = data.status; - $scope.$emit('JobReady'); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job status for job: ' + $scope.job_id + '. GET status: ' + status - }); - }); - - $scope.showEvents = function (host_name, last_job) { - // When click on !Failed Events link, redirect to latest job/job_events for the host - Rest.setUrl(last_job); - Rest.get() - .success(function (data) { - $location.url('/jobs_events/' + data.id + '/?host=' + encodeURI(host_name)); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to lookup last job: ' + last_job + - '. GET status: ' + status }); - }); - }; - - $scope.refresh = function () { - // @issue: OLD SEARCH - // $scope.search(list.iterator); - }; - -} - -JobHostSummaryList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'JobHostList', - 'generateList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', - 'GetBasePath', 'JobStatusToolTip', 'Wait' -]; diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js deleted file mode 100644 index 4b4ef05c19..0000000000 --- a/awx/ui/client/src/controllers/Projects.js +++ /dev/null @@ -1,749 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Projects - * @description This controller's for the projects page - */ - - -export function ProjectsList($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: '
' + i18n._('Are you sure you want to delete the project below?') + '
' + '
' + $filter('sanitize')(name) + '
', - action: action, - actionText: 'DELETE' - }); - }; - - if ($scope.removeCancelUpdate) { - $scope.removeCancelUpdate(); - } - $scope.removeCancelUpdate = $scope.$on('Cancel_Update', function(e, url) { - // Cancel the project update process - Rest.setUrl(url); - Rest.post() - .success(function () { - Alert(i18n._('SCM Update Cancel'), i18n._('Your request to cancel the update was submitted to the task manager.'), 'alert-info'); - $scope.refresh(); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. POST status: '), url) + status }); - }); - }); - - if ($scope.removeCheckCancel) { - $scope.removeCheckCancel(); - } - $scope.removeCheckCancel = $scope.$on('Check_Cancel', function(e, data) { - // Check that we 'can' cancel the update - var url = data.related.cancel; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - if (data.can_cancel) { - $scope.$emit('Cancel_Update', url); - } else { - Alert(i18n._('Cancel Not Allowed'), '
' + i18n.sprintf(i18n._('Either you do not have access or the SCM update process completed. ' + - 'Click the %sRefresh%s button to view the latest status.'), '', '') + '
', 'alert-info', null, null, null, null, true); - } - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. GET status: '), url) + status }); - }); - }); - - $scope.cancelUpdate = function(id, name) { - Rest.setUrl(GetBasePath("projects") + id); - Rest.get() - .success(function(data) { - if (data.related.current_update) { - Rest.setUrl(data.related.current_update); - Rest.get() - .success(function(data) { - $scope.$emit('Check_Cancel', data); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Call to %s failed. GET status: '), data.related.current_update) + status }); - }); - } else { - Alert(i18n._('Update Not Found'), '
' + i18n.sprintf(i18n._('An SCM update does not appear to be running for project: %s. Click the %sRefresh%s ' + - 'button to view the latest status.'), $filter('sanitize')(name), '', '') + '
', 'alert-info',undefined,undefined,undefined,undefined,true); - } - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), - msg: i18n._('Call to get project failed. GET status: ') + status }); - }); - }; - - $scope.SCMUpdate = function(project_id, event) { - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - $scope.projects.every(function(project) { - if (project.id === project_id) { - if (project.scm_type === "Manual" || Empty(project.scm_type)) { - // Do not respond. Button appears greyed out as if it is disabled. Not disabled though, because we need mouse over event - // to work. So user can click, but we just won't do anything. - //Alert('Missing SCM Setup', 'Before running an SCM update, edit the project and provide the SCM access information.', 'alert-info'); - } else if (project.status === 'updating' || project.status === 'running' || project.status === 'pending') { - // Alert('Update in Progress', 'The SCM update process is running. Use the Refresh button to monitor the status.', 'alert-info'); - } else { - ProjectUpdate({ scope: $scope, project_id: project.id }); - } - return false; - } - return true; - }); - }; - - $scope.editSchedules = function(id) { - var project = Find({ list: $scope.projects, key: 'id', val: id }); - if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) { - $state.go('projectSchedules', { id: id }); - } - }; -} - -ProjectsList.$inject = ['$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' -]; - -export function ProjectsAdd($scope, $rootScope, $compile, $location, $log, - $stateParams, GenerateForm, ProjectsForm, Rest, Alert, ProcessErrors, - GetBasePath, GetProjectPath, GetChoices, Wait, $state, CreateSelect2, i18n) { - - var form = ProjectsForm(), - base = $location.path().replace(/^\//, '').split('/')[0], - defaultUrl = GetBasePath('projects'), - master = {}; - - init(); - - function init() { - Rest.setUrl(GetBasePath('projects')); - Rest.options() - .success(function(data) { - if (!data.actions.POST) { - $state.go("^"); - Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a project.'), 'alert-info'); - } - }); - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - } - - GetProjectPath({ scope: $scope, master: master }); - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - var 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"; - //$scope.scm_type = $scope.scm_type_options[i]; - break; - } - } - - CreateSelect2({ - element: '#project_scm_type', - multiple: false - }); - - $scope.scmRequired = false; - master.scm_type = $scope.scm_type; - }); - - // Load the list of options for Kind - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'scm_type', - variable: 'scm_type_options', - callback: 'choicesReady' - }); - CreateSelect2({ - element: '#local-path-select', - multiple: false - }); - - // Save - $scope.formSave = function() { - var i, fld, url, data = {}; - data = {}; - for (fld in form.fields) { - if (form.fields[fld].type === 'checkbox_group') { - for (i = 0; i < form.fields[fld].fields.length; i++) { - data[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name]; - } - } else { - if (form.fields[fld].type !== 'alertblock') { - data[fld] = $scope[fld]; - } - } - } - - if ($scope.scm_type.value === "manual") { - data.scm_type = ""; - data.local_path = $scope.local_path.value; - } else { - data.scm_type = $scope.scm_type.value; - delete data.local_path; - } - - url = (base === 'teams') ? GetBasePath('teams') + $stateParams.team_id + '/projects/' : defaultUrl; - Wait('start'); - Rest.setUrl(url); - Rest.post(data) - .success(function(data) { - $scope.addedItem = data.id; - $state.go('projects.edit', { project_id: data.id }, { reload: true }); - }) - .error(function(data, status) { - Wait('stop'); - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), - msg: i18n._('Failed to create new project. POST returned status: ') + status }); - }); - }; - - $scope.scmChange = function() { - // When an scm_type is set, path is not required - if ($scope.scm_type) { - $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'; - } - - // Dynamically update popover values - if ($scope.scm_type.value) { - switch ($scope.scm_type.value) { - case 'git': - $scope.urlPopover = '

' + - i18n._('Example URLs for GIT SCM include:') + - '

  • https://github.com/ansible/ansible.git
  • ' + - '
  • git@github.com:ansible/ansible.git
  • git://servername.example.com/ansible.git
' + - '

' + 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:') + '

' + - '
  • https://github.com/ansible/ansible
  • svn://servername.example.com/path
  • ' + - '
  • svn+ssh://servername.example.com/path
'; - break; - case 'hg': - $scope.urlPopover = '

' + i18n._('Example URLs for Mercurial SCM include:') + '

' + - '
  • https://bitbucket.org/username/project
  • ssh://hg@bitbucket.org/username/project
  • ' + - '
  • ssh://server.example.com/path
' + - '

' + 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.sprintf(i18n._('Are you sure you want to remove the %s below from %s?'), title, $scope.name) + '
' + '
' + name + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; - - $scope.scmChange = function() { - if ($scope.scm_type) { - $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') ? i18n._('Revision #') : i18n._('SCM Branch'); - } - - // Dynamically update popover values - if ($scope.scm_type.value) { - switch ($scope.scm_type.value) { - case 'git': - $scope.urlPopover = '

' + i18n._('Example URLs for GIT SCM include:') + '

  • https://github.com/ansible/ansible.git
  • ' + - '
  • git@github.com:ansible/ansible.git
  • git://servername.example.com/ansible.git
' + - '

' + 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:') + '

' + - '
  • https://github.com/ansible/ansible
  • svn://servername.example.com/path
  • ' + - '
  • svn+ssh://servername.example.com/path
'; - break; - case 'hg': - $scope.urlPopover = '

' + i18n._('Example URLs for Mercurial SCM include:') + '

' + - '
  • https://bitbucket.org/username/project
  • ssh://hg@bitbucket.org/username/project
  • ' + - '
  • ssh://server.example.com/path
' + - '

' + 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 = "

Socket url: {{ socket_url }}  Status: {{ socket_status }} {{ socket_reason }}
\n" + - "
\n" + - "
Received Messages:
\n" + - "
\n" + - "
\n" + - "
    \n" + - "
  • {{ message }}
  • \n" + - "
\n" + - "
\n"; - - e = angular.element(document.getElementById('test-container')); - e.append(html); - $compile(e)(test_scope); - e = angular.element(document.getElementById('schedules-container')); - e.append(html); - $compile(e)(schedules_scope); - e = angular.element(document.getElementById('jobs-container')); - e.append(html); - $compile(e)(jobs_scope); - - html = "
\n" + - "
\n" + - "
Socket url: {{ socket_url }}  Status: {{ socket_status }} {{ socket_reason }}
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "\n" + - "\n" + - "
\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - "
" + - "

Subscribed to events for job: {{ jobs_list }}

\n" + - "
Received Messages:
\n" + - "
\n" + - "
\n" + - "
    \n" + - "
  • {{ message }}
  • \n" + - "
\n" + - "
\n"; - - e = angular.element(document.getElementById('job-events-container')); - e.append(html); - $compile(e)(job_events_scope); - - schedules_scope.url = schedules_socket.getUrl(); - test_scope.url = test_socket.getUrl(); - jobs_scope.url = jobs_socket.getUrl(); - job_events_scope.url = job_events_socket.getUrl(); - - test_scope.messages.push('Message Displayed Before Connection'); - - test_socket.on('test', function(data) { - test_scope.messages.push(data); - }); - - schedules_socket.on("schedule_changed", function(data) { - schedules_scope.messages.push(data); - }); - - jobs_socket.on("status_changed", function(data) { - jobs_scope.messages.push(data); - }); - - jobs_socket.on("summary_complete", function(data) { - jobs_scope.messages.push(data); - }); - - job_events_scope.jobs_list = []; - - job_events_scope.subscribeToJobEvent = function() { - job_events_scope.jobs_list.push(job_events_scope.job_id); - job_events_socket.on("job_events-" + job_events_scope.job_id, function(data) { - job_events_scope.messages.push(data); - setTimeout(function() { - $(document).scrollTop($(document).prop("scrollHeight")); - $('#event-message-container').scrollTop($('#event-message-container').prop("scrollHeight")); - }, 300); - }); - }; -} - -SocketsController.$inject = [ '$scope', '$compile', 'ClearScope', 'Socket']; diff --git a/awx/ui/client/src/controllers/Teams.js b/awx/ui/client/src/controllers/Teams.js deleted file mode 100644 index 464a10ee10..0000000000 --- a/awx/ui/client/src/controllers/Teams.js +++ /dev/null @@ -1,247 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Teams - * @description This controller's for teams - */ - -export function TeamsList($scope, $rootScope, $log, $stateParams, - Rest, Alert, TeamList, Prompt, ClearScope, ProcessErrors, - GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) { - - ClearScope(); - - var list = TeamList, - defaultUrl = GetBasePath('teams'); - - init(); - - function init() { - $scope.canAdd = false; - - rbacUiControlService.canAdd('teams') - .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], (team) => { - team.organization_name = team.summary_fields.organization.name; - }); - - $scope.selected = []; - } - - $scope.addTeam = function() { - $state.go('teams.add'); - }; - - $scope.editTeam = function(id) { - $state.go('teams.edit', { team_id: id }); - }; - - $scope.deleteTeam = function(id, name) { - - var action = function() { - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .success(function() { - Wait('stop'); - $('#prompt-modal').modal('hide'); - if (parseInt($state.params.team_id) === id) { - $state.go('^', null, { reload: true }); - } else { - $state.go('.', null, { reload: true }); - } - }) - .error(function(data, status) { - Wait('stop'); - $('#prompt-modal').modal('hide'); - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - Prompt({ - hdr: 'Delete', - body: '
Are you sure you want to delete the team below?
' + $filter('sanitize')(name) + '
', - action: action, - actionText: 'DELETE' - }); - }; -} - - -TeamsList.$inject = ['$scope', '$rootScope', '$log', - '$stateParams', 'Rest', 'Alert', 'TeamList', 'Prompt', 'ClearScope', - 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset' -]; - - -export function TeamsAdd($scope, $rootScope, $stateParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors, - ClearScope, GetBasePath, Wait, $state) { - ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior - //$scope. - - Rest.setUrl(GetBasePath('teams')); - Rest.options() - .success(function(data) { - if (!data.actions.POST) { - $state.go("^"); - Alert('Permission Error', 'You do not have permission to add a team.', 'alert-info'); - } - }); - - // Inject dynamic view - var defaultUrl = GetBasePath('teams'), - form = TeamForm; - - init(); - - function init() { - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - $rootScope.flashMessage = null; - } - - // Save - $scope.formSave = function() { - var fld, data; - GenerateForm.clearApiErrors($scope); - Wait('start'); - Rest.setUrl(defaultUrl); - data = {}; - for (fld in form.fields) { - data[fld] = $scope[fld]; - } - Rest.post(data) - .success(function(data) { - Wait('stop'); - $rootScope.flashMessage = "New team successfully created!"; - $rootScope.$broadcast("EditIndicatorChange", "users", data.id); - $state.go('teams.edit', { team_id: data.id }, { reload: true }); - }) - .error(function(data, status) { - Wait('stop'); - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new team. Post returned status: ' + - status - }); - }); - }; - - $scope.formCancel = function() { - $state.go('teams'); - }; -} - -TeamsAdd.$inject = ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'GenerateForm', - 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state' -]; - - -export function TeamsEdit($scope, $rootScope, $stateParams, - TeamForm, Rest, ProcessErrors, ClearScope, GetBasePath, Wait, $state) { - - ClearScope(); - - var form = TeamForm, - id = $stateParams.team_id, - defaultUrl = GetBasePath('teams') + id; - - init(); - - function init() { - $scope.team_id = id; - Rest.setUrl(defaultUrl); - Wait('start'); - Rest.get(defaultUrl).success(function(data) { - setScopeFields(data); - $scope.organization_name = data.summary_fields.organization.name; - - $scope.team_obj = data; - Wait('stop'); - }); - - $scope.$watch('team_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - - } - - // @issue I think all this really want to do is _.forEach(form.fields, (field) =>{ $scope[field] = data[field]}) - function setScopeFields(data) { - _(data) - .pick(function(value, key) { - return form.fields.hasOwnProperty(key) === true; - }) - .forEach(function(value, key) { - $scope[key] = value; - }) - .value(); - return; - } - - // prepares a data payload for a PUT request to the API - function processNewData(fields) { - var data = {}; - _.forEach(fields, function(value, key) { - if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) { - data[key] = $scope[key]; - } - }); - return data; - } - - $scope.formCancel = function() { - $state.go('teams', null, { reload: true }); - }; - - $scope.formSave = function() { - $rootScope.flashMessage = null; - if ($scope[form.name + '_form'].$valid) { - var data = processNewData(form.fields); - Rest.setUrl(defaultUrl); - Rest.put(data).success(function() { - $state.go($state.current, null, { reload: true }); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Failed to retrieve user: ' + - $stateParams.id + '. GET status: ' + status - }); - }); - } - }; - - init(); - - $scope.convertApiUrl = function(str) { - if (str) { - return str.replace("api/v1", "#"); - } else { - return null; - } - }; -} - -TeamsEdit.$inject = ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'Rest', - 'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state' -]; diff --git a/awx/ui/client/src/controllers/Users.js b/awx/ui/client/src/controllers/Users.js deleted file mode 100644 index a305a3e133..0000000000 --- a/awx/ui/client/src/controllers/Users.js +++ /dev/null @@ -1,357 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Users - * @description This controller's the Users page - */ - -import { N_ } from "../i18n"; - -const user_type_options = [ - { type: 'normal', label: N_('Normal User') }, - { type: 'system_auditor', label: N_('System Auditor') }, - { type: 'system_administrator', label: N_('System Administrator') }, -]; - -function user_type_sync($scope) { - return (type_option) => { - $scope.is_superuser = false; - $scope.is_system_auditor = false; - switch (type_option.type) { - case 'system_administrator': - $scope.is_superuser = true; - break; - case 'system_auditor': - $scope.is_system_auditor = true; - break; - } - }; -} - -export function UsersList($scope, $rootScope, $stateParams, - Rest, Alert, UserList, Prompt, ClearScope, ProcessErrors, GetBasePath, - Wait, $state, $filter, rbacUiControlService, Dataset, i18n) { - - for (var i = 0; i < user_type_options.length; i++) { - user_type_options[i].label = i18n._(user_type_options[i].label); - } - - ClearScope(); - - var list = UserList, - defaultUrl = GetBasePath('users'); - - init(); - - function init() { - $scope.canAdd = false; - - rbacUiControlService.canAdd('users') - .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; - - - $rootScope.flashMessage = null; - $scope.selected = []; - } - - $scope.addUser = function() { - $state.go('users.add'); - }; - - $scope.editUser = function(id) { - $state.go('users.edit', { user_id: id }); - }; - - $scope.deleteUser = 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.user_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 - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - body: '
' + i18n._('Are you sure you want to delete the user below?') + '
' + $filter('sanitize')(name) + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; -} - -UsersList.$inject = ['$scope', '$rootScope', '$stateParams', - 'Rest', 'Alert', 'UserList', 'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath', - 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n' -]; - - -export function UsersAdd($scope, $rootScope, $stateParams, UserForm, - GenerateForm, Rest, Alert, ProcessErrors, ReturnToCaller, ClearScope, - GetBasePath, ResetForm, Wait, CreateSelect2, $state, $location, i18n) { - - ClearScope(); - - var defaultUrl = GetBasePath('organizations'), - form = UserForm; - - init(); - - function init() { - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - $scope.ldap_user = false; - $scope.not_ldap_user = !$scope.ldap_user; - $scope.ldap_dn = null; - $scope.socialAuthUser = false; - $scope.external_account = null; - - Rest.setUrl(GetBasePath('users')); - Rest.options() - .success(function(data) { - if (!data.actions.POST) { - $state.go("^"); - Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a user.'), 'alert-info'); - } - }); - - $scope.user_type_options = user_type_options; - $scope.user_type = user_type_options[0]; - $scope.$watch('user_type', user_type_sync($scope)); - CreateSelect2({ - element: '#user_user_type', - multiple: false - }); - } - - // Save - $scope.formSave = function() { - var fld, data = {}; - if ($scope[form.name + '_form'].$valid) { - if ($scope.organization !== undefined && $scope.organization !== null && $scope.organization !== '') { - Rest.setUrl(defaultUrl + $scope.organization + '/users/'); - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - data.is_superuser = $scope.is_superuser; - data.is_system_auditor = $scope.is_system_auditor; - Wait('start'); - Rest.post(data) - .success(function(data) { - var base = $location.path().replace(/^\//, '').split('/')[0]; - if (base === 'users') { - $rootScope.flashMessage = i18n._('New user successfully created!'); - $rootScope.$broadcast("EditIndicatorChange", "users", data.id); - $state.go('users.edit', { user_id: data.id }, { reload: true }); - } else { - ReturnToCaller(1); - } - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n._('Failed to add new user. POST returned status: ') + status }); - }); - } else { - $scope.organization_name_api_error = i18n._('A value is required'); - } - } - }; - - $scope.formCancel = function() { - $state.go('users'); - }; - - // Password change - $scope.clearPWConfirm = function(fld) { - // If password value changes, make sure password_confirm must be re-entered - $scope[fld] = ''; - $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); - }; -} - -UsersAdd.$inject = ['$scope', '$rootScope', '$stateParams', 'UserForm', 'GenerateForm', - 'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath', - 'ResetForm', 'Wait', 'CreateSelect2', '$state', '$location', 'i18n' -]; - -export function UsersEdit($scope, $rootScope, $location, - $stateParams, UserForm, Rest, ProcessErrors, - ClearScope, GetBasePath, ResetForm, Wait, CreateSelect2, $state, i18n) { - - for (var i = 0; i < user_type_options.length; i++) { - user_type_options[i].label = i18n._(user_type_options[i].label); - } - ClearScope(); - - var form = UserForm, - master = {}, - id = $stateParams.user_id, - defaultUrl = GetBasePath('users') + id; - - init(); - - function init() { - $scope.hidePagination = false; - $scope.hideSmartSearch = false; - $scope.user_type_options = user_type_options; - $scope.user_type = user_type_options[0]; - $scope.$watch('user_type', user_type_sync($scope)); - $scope.$watch('is_superuser', hidePermissionsTabSmartSearchAndPaginationIfSuperUser($scope)); - Rest.setUrl(defaultUrl); - Wait('start'); - Rest.get(defaultUrl).success(function(data) { - $scope.user_id = id; - $scope.ldap_user = (data.ldap_dn !== null && data.ldap_dn !== undefined && data.ldap_dn !== '') ? true : false; - $scope.not_ldap_user = !$scope.ldap_user; - master.ldap_user = $scope.ldap_user; - $scope.socialAuthUser = (data.auth.length > 0) ? true : false; - $scope.external_account = data.external_account; - - $scope.user_type = $scope.user_type_options[0]; - $scope.is_system_auditor = false; - $scope.is_superuser = false; - if (data.is_system_auditor) { - $scope.user_type = $scope.user_type_options[1]; - $scope.is_system_auditor = true; - } - if (data.is_superuser) { - $scope.user_type = $scope.user_type_options[2]; - $scope.is_superuser = true; - } - - $scope.user_obj = data; - $scope.name = data.username; - - CreateSelect2({ - element: '#user_user_type', - multiple: false - }); - - $scope.$watch('user_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - setScopeFields(data); - Wait('stop'); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed to retrieve user: %s. GET status: '), $stateParams.id) + status - }); - }); - } - - // Organizations and Teams tab pagination is hidden through other mechanism - function hidePermissionsTabSmartSearchAndPaginationIfSuperUser(scope) { - return function(isSuperuserNewValue) { - let shouldHide = isSuperuserNewValue; - if (shouldHide === true) { - scope.hidePagination = true; - scope.hideSmartSearch = true; - } else if (shouldHide === false) { - scope.hidePagination = false; - scope.hideSmartSearch = false; - } - }; - } - - - function setScopeFields(data) { - _(data) - .pick(function(value, key) { - return form.fields.hasOwnProperty(key) === true; - }) - .forEach(function(value, key) { - $scope[key] = value; - }) - .value(); - return; - } - - $scope.convertApiUrl = function(str) { - if (str) { - return str.replace("api/v1", "#"); - } else { - return null; - } - }; - - // prepares a data payload for a PUT request to the API - var processNewData = function(fields) { - var data = {}; - _.forEach(fields, function(value, key) { - if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) { - data[key] = $scope[key]; - } - }); - data.is_superuser = $scope.is_superuser; - data.is_system_auditor = $scope.is_system_auditor; - return data; - }; - - $scope.formCancel = function() { - $state.go('users', null, { reload: true }); - }; - - $scope.formSave = function() { - $rootScope.flashMessage = null; - if ($scope[form.name + '_form'].$valid) { - Rest.setUrl(defaultUrl + '/'); - var data = processNewData(form.fields); - Rest.put(data).success(function() { - $state.go($state.current, null, { reload: true }); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed to retrieve user: %s. GET status: '), $stateParams.id) + status - }); - }); - } - }; - - $scope.clearPWConfirm = function(fld) { - // If password value changes, make sure password_confirm must be re-entered - $scope[fld] = ''; - $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); - $rootScope.flashMessage = null; - }; -} - -UsersEdit.$inject = ['$scope', '$rootScope', '$location', - '$stateParams', 'UserForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath', - 'ResetForm', 'Wait', 'CreateSelect2', '$state', 'i18n' -]; diff --git a/awx/ui/client/src/credentials/add/credentials-add.controller.js b/awx/ui/client/src/credentials/add/credentials-add.controller.js new file mode 100644 index 0000000000..fa6c436e66 --- /dev/null +++ b/awx/ui/client/src/credentials/add/credentials-add.controller.js @@ -0,0 +1,177 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$compile', '$location', + '$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert', + 'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange', + 'OwnerChange', 'FormSave', '$state', 'CreateSelect2', 'i18n', + function($scope, $rootScope, $compile, $location, $log, + $stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors, + ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange, + OwnerChange, FormSave, $state, CreateSelect2, i18n) { + + ClearScope(); + + // Inject dynamic view + var form = CredentialForm, + defaultUrl = GetBasePath('credentials'), + url; + + init(); + + function init() { + // Load the list of options for Kind + GetChoices({ + scope: $scope, + url: defaultUrl, + field: 'kind', + variable: 'credential_kind_options' + }); + + GetChoices({ + scope: $scope, + url: defaultUrl, + field: 'become_method', + variable: 'become_options' + }); + + CreateSelect2({ + element: '#credential_become_method', + multiple: false + }); + + CreateSelect2({ + element: '#credential_kind', + multiple: false + }); + + // apply form definition's default field values + GenerateForm.applyDefaults(form, $scope); + + $scope.keyEntered = false; + $scope.permissionsTooltip = i18n._('Please save before assigning permissions'); + + // determine if the currently logged-in user may share this credential + // previous commentary said: "$rootScope.current_user isn't available because a call to the config endpoint hasn't finished resolving yet" + // I'm 99% sure this state's will never resolve block will be rejected if setup surrounding config endpoint hasn't completed + if ($rootScope.current_user && $rootScope.current_user.is_superuser) { + $scope.canShareCredential = true; + } else { + Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`); + Rest.get() + .success(function(data) { + $scope.canShareCredential = (data.count) ? true : false; + }).error(function(data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status }); + }); + } + } + + if (!Empty($stateParams.user_id)) { + // Get the username based on incoming route + $scope.owner = 'user'; + $scope.user = $stateParams.user_id; + OwnerChange({ scope: $scope }); + url = GetBasePath('users') + $stateParams.user_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + $scope.user_username = data.username; + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user. GET status: ' + status }); + }); + } else if (!Empty($stateParams.team_id)) { + // Get the username based on incoming route + $scope.owner = 'team'; + $scope.team = $stateParams.team_id; + OwnerChange({ scope: $scope }); + url = GetBasePath('teams') + $stateParams.team_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + $scope.team_name = data.name; + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve team. GET status: ' + status }); + }); + } else { + // default type of owner to a user + $scope.owner = 'user'; + OwnerChange({ scope: $scope }); + } + + $scope.$watch("ssh_key_data", function(val) { + if (val === "" || val === null || val === undefined) { + $scope.keyEntered = false; + $scope.ssh_key_unlock_ask = false; + $scope.ssh_key_unlock = ""; + } else { + $scope.keyEntered = true; + } + }); + + // Handle Kind change + $scope.kindChange = function() { + KindChange({ scope: $scope, form: form, reset: true }); + }; + + $scope.becomeMethodChange = function() { + BecomeMethodChange({ scope: $scope }); + }; + + // Save + $scope.formSave = function() { + if ($scope[form.name + '_form'].$valid) { + FormSave({ scope: $scope, mode: 'add' }); + } + }; + + $scope.formCancel = function() { + $state.go('credentials'); + }; + + // Password change + $scope.clearPWConfirm = function(fld) { + // If password value changes, make sure password_confirm must be re-entered + $scope[fld] = ''; + $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); + }; + + // Respond to 'Ask at runtime?' checkbox + $scope.ask = function(fld, associated) { + if ($scope[fld + '_ask']) { + $scope[fld] = 'ASK'; + $("#" + form.name + "_" + fld + "_input").attr("type", "text"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); + if (associated !== "undefined") { + $("#" + form.name + "_" + fld + "_input").attr("type", "password"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } + } else { + $scope[fld] = ''; + $("#" + form.name + "_" + fld + "_input").attr("type", "password"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); + if (associated !== "undefined") { + $("#" + form.name + "_" + fld + "_input").attr("type", "text"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } + } + }; + + // Click clear button + $scope.clear = function(fld, associated) { + $scope[fld] = ''; + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + $scope[form.name + '_form'].$setDirty(); + }; + } +]; diff --git a/awx/ui/client/src/credentials/edit/credentials-edit.controller.js b/awx/ui/client/src/credentials/edit/credentials-edit.controller.js new file mode 100644 index 0000000000..1926e811ca --- /dev/null +++ b/awx/ui/client/src/credentials/edit/credentials-edit.controller.js @@ -0,0 +1,346 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$compile', '$location', + '$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert', + 'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices', + 'KindChange', 'BecomeMethodChange', 'Empty', 'OwnerChange', + 'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n', + function($scope, $rootScope, $compile, $location, $log, + $stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt, + GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, FormSave, Wait, + $state, CreateSelect2, Authorization, i18n) { + + ClearScope(); + + var defaultUrl = GetBasePath('credentials'), + form = CredentialForm, + base = $location.path().replace(/^\//, '').split('/')[0], + master = {}, + id = $stateParams.credential_id; + + init(); + + function init() { + $scope.id = id; + $scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) { + if (val === false) { + $scope.canAdd = false; + } + }); + + $scope.canShareCredential = false; + Wait('start'); + if (!$rootScope.current_user) { + Authorization.restoreUserInfo(); + } + GetChoices({ + scope: $scope, + url: defaultUrl, + field: 'kind', + variable: 'credential_kind_options', + callback: 'choicesReadyCredential' + }); + + GetChoices({ + scope: $scope, + url: defaultUrl, + field: 'become_method', + variable: 'become_options' + }); + + if ($rootScope.current_user && $rootScope.current_user.is_superuser) { + $scope.canShareCredential = true; + } else { + Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`); + Rest.get() + .success(function(data) { + $scope.canShareCredential = (data.count) ? true : false; + Wait('stop'); + }).error(function(data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status }); + }); + } + + $scope.$watch('organization', function(val) { + if (val === undefined) { + $scope.permissionsTooltip = i18n._('Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.'); + } else { + $scope.permissionsTooltip = ''; + } + }); + + setAskCheckboxes(); + OwnerChange({ scope: $scope }); + $scope.$watch("ssh_key_data", function(val) { + if (val === "" || val === null || val === undefined) { + $scope.keyEntered = false; + $scope.ssh_key_unlock_ask = false; + $scope.ssh_key_unlock = ""; + } else { + $scope.keyEntered = true; + } + }); + } + + function setAskCheckboxes() { + var fld, i; + for (fld in form.fields) { + if (form.fields[fld].type === 'sensitive' && $scope[fld] === 'ASK') { + // turn on 'ask' checkbox for password fields with value of 'ASK' + $("#" + form.name + "_" + fld + "_input").attr("type", "text"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); + $("#" + fld + "-clear-btn").attr("disabled", "disabled"); + $scope[fld + '_ask'] = true; + } else { + $scope[fld + '_ask'] = false; + $("#" + fld + "-clear-btn").removeAttr("disabled"); + } + master[fld + '_ask'] = $scope[fld + '_ask']; + } + + // Set kind field to the correct option + for (i = 0; i < $scope.credential_kind_options.length; i++) { + if ($scope.kind === $scope.credential_kind_options[i].value) { + $scope.kind = $scope.credential_kind_options[i]; + break; + } + } + } + if ($scope.removeChoicesReady) { + $scope.removeChoicesReady(); + } + $scope.removeChoicesReady = $scope.$on('choicesReadyCredential', function() { + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl + ':id/'); + Rest.get({ params: { id: id } }) + .success(function(data) { + if (data && data.summary_fields && + data.summary_fields.organization && + data.summary_fields.organization.id) { + $scope.needsRoleList = true; + } else { + $scope.needsRoleList = false; + } + + $scope.credential_name = data.name; + + var i, fld; + + + for (fld in form.fields) { + if (data[fld] !== null && data[fld] !== undefined) { + $scope[fld] = data[fld]; + master[fld] = $scope[fld]; + } + if (form.fields[fld].type === 'lookup' && 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] = + $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; + } + } + + if (!Empty($scope.user)) { + $scope.owner = 'user'; + } else { + $scope.owner = 'team'; + } + master.owner = $scope.owner; + + for (i = 0; i < $scope.become_options.length; i++) { + if ($scope.become_options[i].value === data.become_method) { + $scope.become_method = $scope.become_options[i]; + break; + } + } + + if ($scope.become_method && $scope.become_method.value === "") { + $scope.become_method = null; + } + master.become_method = $scope.become_method; + + $scope.$watch('become_method', function(val) { + if (val !== null) { + if (val.value === "") { + $scope.become_username = ""; + $scope.become_password = ""; + } + } + }); + + for (i = 0; i < $scope.credential_kind_options.length; i++) { + if ($scope.credential_kind_options[i].value === data.kind) { + $scope.kind = $scope.credential_kind_options[i]; + break; + } + } + + KindChange({ + scope: $scope, + form: form, + reset: false + }); + + master.kind = $scope.kind; + + CreateSelect2({ + element: '#credential_become_method', + multiple: false + }); + + CreateSelect2({ + element: '#credential_kind', + multiple: false + }); + + switch (data.kind) { + case 'aws': + $scope.access_key = data.username; + $scope.secret_key = data.password; + master.access_key = $scope.access_key; + master.secret_key = $scope.secret_key; + break; + case 'ssh': + $scope.ssh_password = data.password; + master.ssh_password = $scope.ssh_password; + break; + case 'rax': + $scope.api_key = data.password; + master.api_key = $scope.api_key; + break; + case 'gce': + $scope.email_address = data.username; + $scope.project = data.project; + break; + case 'azure': + $scope.subscription = data.username; + break; + } + $scope.credential_obj = data; + + $scope.$emit('credentialLoaded'); + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to retrieve Credential: ' + $stateParams.id + '. GET status: ' + status + }); + }); + }); + + // Save changes to the parent + $scope.formSave = function() { + if ($scope[form.name + '_form'].$valid) { + FormSave({ scope: $scope, mode: 'edit' }); + } + }; + + // Handle Owner change + $scope.ownerChange = function() { + OwnerChange({ scope: $scope }); + }; + + // Handle Kind change + $scope.kindChange = function() { + KindChange({ scope: $scope, form: form, reset: true }); + }; + + $scope.becomeMethodChange = function() { + BecomeMethodChange({ scope: $scope }); + }; + + $scope.formCancel = function() { + $state.transitionTo('credentials'); + }; + + // Related set: Add button + $scope.add = function(set) { + $rootScope.flashMessage = null; + $location.path('/' + base + '/' + $stateParams.id + '/' + set + '/add'); + }; + + // Related set: Edit button + $scope.edit = function(set, id) { + $rootScope.flashMessage = null; + $location.path('/' + base + '/' + $stateParams.id + '/' + set + '/' + id); + }; + + // Related set: Delete button + $scope['delete'] = function(set, itm_id, name, title) { + $rootScope.flashMessage = null; + + var action = function() { + var url = defaultUrl + id + '/' + set + '/'; + 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: 'Error!', + msg: 'Call to ' + url + ' failed. POST returned status: ' + status + }); + }); + }; + + Prompt({ + hdr: i18n._('Delete'), + body: '
' + i18n.sprintf(i18n._('Are you sure you want to remove the %s below from %s?'), title, $scope.name) + '
' + name + '
', + action: action, + actionText: i18n._('DELETE') + }); + + }; + + // Password change + $scope.clearPWConfirm = function(fld) { + // If password value changes, make sure password_confirm must be re-entered + $scope[fld] = ''; + $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); + }; + + // Respond to 'Ask at runtime?' checkbox + $scope.ask = function(fld, associated) { + if ($scope[fld + '_ask']) { + $scope[fld] = 'ASK'; + $("#" + form.name + "_" + fld + "_input").attr("type", "text"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); + if (associated !== "undefined") { + $("#" + form.name + "_" + fld + "_input").attr("type", "password"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } + } else { + $scope[fld] = ''; + $("#" + form.name + "_" + fld + "_input").attr("type", "password"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Show"); + if (associated !== "undefined") { + $("#" + form.name + "_" + fld + "_input").attr("type", "text"); + $("#" + form.name + "_" + fld + "_show_input_button").html("Hide"); + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } + } + }; + + $scope.clear = function(fld, associated) { + $scope[fld] = ''; + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + $scope[form.name + '_form'].$setDirty(); + }; + } +]; diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js new file mode 100644 index 0000000000..859e0ea998 --- /dev/null +++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js @@ -0,0 +1,106 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$location', '$log', + '$stateParams', 'Rest', 'Alert', 'CredentialList', 'Prompt', 'ClearScope', + 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n', + function($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: '
' + i18n._('Are you sure you want to delete the credential below?') + '
' + $filter('sanitize')(name) + '
', + action: action, + actionText: i18n._('DELETE') + }); + }; + } +]; diff --git a/awx/ui/client/src/credentials/main.js b/awx/ui/client/src/credentials/main.js index faee3f5093..16cc13aad0 100644 --- a/awx/ui/client/src/credentials/main.js +++ b/awx/ui/client/src/credentials/main.js @@ -5,7 +5,45 @@ *************************************************/ import ownerList from './ownerList.directive'; +import CredentialsList from './list/credentials-list.controller'; +import CredentialsAdd from './add/credentials-add.controller'; +import CredentialsEdit from './edit/credentials-edit.controller'; +import { N_ } from '../i18n'; export default angular.module('credentials', []) - .directive('ownerList', ownerList); + .directive('ownerList', ownerList) + .controller('CredentialsList', CredentialsList) + .controller('CredentialsAdd', CredentialsAdd) + .controller('CredentialsEdit', CredentialsEdit) + .config(['$stateProvider', 'stateDefinitionsProvider', + function($stateProvider, stateDefinitionsProvider) { + let stateDefinitions = stateDefinitionsProvider.$get(); + + // 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: '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') + } + }) + }); + } + ]); diff --git a/awx/ui/client/src/dashboard/counts/dashboard-counts.block.less b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less similarity index 97% rename from awx/ui/client/src/dashboard/counts/dashboard-counts.block.less rename to awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less index d9a85ed444..8d85f93bf5 100644 --- a/awx/ui/client/src/dashboard/counts/dashboard-counts.block.less +++ b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less @@ -1,4 +1,4 @@ -@import "../../shared/branding/colors.default.less"; +@import "../../../shared/branding/colors.default.less"; /** @define DashboardCounts */ diff --git a/awx/ui/client/src/dashboard/counts/dashboard-counts.directive.js b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.directive.js similarity index 97% rename from awx/ui/client/src/dashboard/counts/dashboard-counts.directive.js rename to awx/ui/client/src/home/dashboard/counts/dashboard-counts.directive.js index 8d26b9d1c6..bc91c9c8ba 100644 --- a/awx/ui/client/src/dashboard/counts/dashboard-counts.directive.js +++ b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.directive.js @@ -9,7 +9,7 @@ export default data: '=' }, replace: false, - templateUrl: templateUrl('dashboard/counts/dashboard-counts'), + templateUrl: templateUrl('home/dashboard/counts/dashboard-counts'), link: function(scope, element, attrs) { scope.$watch("data", function(data) { if (data && data.hosts) { diff --git a/awx/ui/client/src/dashboard/counts/dashboard-counts.partial.html b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.partial.html similarity index 100% rename from awx/ui/client/src/dashboard/counts/dashboard-counts.partial.html rename to awx/ui/client/src/home/dashboard/counts/dashboard-counts.partial.html diff --git a/awx/ui/client/src/dashboard/counts/main.js b/awx/ui/client/src/home/dashboard/counts/main.js similarity index 100% rename from awx/ui/client/src/dashboard/counts/main.js rename to awx/ui/client/src/home/dashboard/counts/main.js diff --git a/awx/ui/client/src/dashboard/dashboard.block.less b/awx/ui/client/src/home/dashboard/dashboard.block.less similarity index 93% rename from awx/ui/client/src/dashboard/dashboard.block.less rename to awx/ui/client/src/home/dashboard/dashboard.block.less index 40bcd84002..f1b6446fa6 100644 --- a/awx/ui/client/src/dashboard/dashboard.block.less +++ b/awx/ui/client/src/home/dashboard/dashboard.block.less @@ -1,5 +1,5 @@ /** @define Dashboard */ -@import "../shared/branding/colors.default.less"; +@import "../../shared/branding/colors.default.less"; .Dashboard { display: flex; diff --git a/awx/ui/client/src/dashboard/dashboard.directive.js b/awx/ui/client/src/home/dashboard/dashboard.directive.js similarity index 80% rename from awx/ui/client/src/dashboard/dashboard.directive.js rename to awx/ui/client/src/home/dashboard/dashboard.directive.js index 243a423a67..b7bf23d019 100644 --- a/awx/ui/client/src/dashboard/dashboard.directive.js +++ b/awx/ui/client/src/home/dashboard/dashboard.directive.js @@ -5,7 +5,7 @@ export default return { restrict: 'E', scope: true, - templateUrl: templateUrl('dashboard/dashboard'), + templateUrl: templateUrl('home/dashboard/dashboard'), link: function(scope, element, attrs) { } }; diff --git a/awx/ui/client/src/dashboard/dashboard.partial.html b/awx/ui/client/src/home/dashboard/dashboard.partial.html similarity index 100% rename from awx/ui/client/src/dashboard/dashboard.partial.html rename to awx/ui/client/src/home/dashboard/dashboard.partial.html diff --git a/awx/ui/client/src/dashboard/graphs/dashboard-graphs.block.less b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less similarity index 98% rename from awx/ui/client/src/dashboard/graphs/dashboard-graphs.block.less rename to awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less index 09cdbf7b72..5de5162761 100644 --- a/awx/ui/client/src/dashboard/graphs/dashboard-graphs.block.less +++ b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less @@ -1,6 +1,6 @@ /** @define DashboardGraphs */ -@import "../../shared/branding/colors.default.less"; +@import "../../../shared/branding/colors.default.less"; .DashboardGraphs { margin-top: 20px; diff --git a/awx/ui/client/src/dashboard/graphs/dashboard-graphs.directive.js b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.directive.js similarity index 86% rename from awx/ui/client/src/dashboard/graphs/dashboard-graphs.directive.js rename to awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.directive.js index 64fb391738..80a0d4e181 100644 --- a/awx/ui/client/src/dashboard/graphs/dashboard-graphs.directive.js +++ b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.directive.js @@ -4,7 +4,7 @@ export default ['templateUrl', return { restrict: 'E', scope: true, - templateUrl: templateUrl('dashboard/graphs/dashboard-graphs'), + templateUrl: templateUrl('home/dashboard/graphs/dashboard-graphs'), link: function(scope, element, attrs) { function clearStatus() { diff --git a/awx/ui/client/src/dashboard/graphs/dashboard-graphs.partial.html b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.partial.html similarity index 100% rename from awx/ui/client/src/dashboard/graphs/dashboard-graphs.partial.html rename to awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.partial.html diff --git a/awx/ui/client/src/dashboard/graphs/graph-helpers/adjust-graph-size.service.js b/awx/ui/client/src/home/dashboard/graphs/graph-helpers/adjust-graph-size.service.js similarity index 100% rename from awx/ui/client/src/dashboard/graphs/graph-helpers/adjust-graph-size.service.js rename to awx/ui/client/src/home/dashboard/graphs/graph-helpers/adjust-graph-size.service.js diff --git a/awx/ui/client/src/dashboard/graphs/graph-helpers/auto-size.directive.js b/awx/ui/client/src/home/dashboard/graphs/graph-helpers/auto-size.directive.js similarity index 100% rename from awx/ui/client/src/dashboard/graphs/graph-helpers/auto-size.directive.js rename to awx/ui/client/src/home/dashboard/graphs/graph-helpers/auto-size.directive.js diff --git a/awx/ui/client/src/dashboard/graphs/graph-helpers/main.js b/awx/ui/client/src/home/dashboard/graphs/graph-helpers/main.js similarity index 100% rename from awx/ui/client/src/dashboard/graphs/graph-helpers/main.js rename to awx/ui/client/src/home/dashboard/graphs/graph-helpers/main.js diff --git a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.directive.js b/awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js similarity index 98% rename from awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.directive.js rename to awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js index 8202fbc21d..d6a289b953 100644 --- a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.directive.js +++ b/awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js @@ -23,7 +23,7 @@ function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustG scope: { data: '=' }, - templateUrl: templateUrl('dashboard/graphs/job-status/job_status_graph'), + templateUrl: templateUrl('home/dashboard/graphs/job-status/job_status_graph'), link: link }; diff --git a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js b/awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.service.js similarity index 100% rename from awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js rename to awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.service.js diff --git a/awx/ui/client/src/dashboard/graphs/job-status/job_status_graph.partial.html b/awx/ui/client/src/home/dashboard/graphs/job-status/job_status_graph.partial.html similarity index 100% rename from awx/ui/client/src/dashboard/graphs/job-status/job_status_graph.partial.html rename to awx/ui/client/src/home/dashboard/graphs/job-status/job_status_graph.partial.html diff --git a/awx/ui/client/src/dashboard/graphs/job-status/main.js b/awx/ui/client/src/home/dashboard/graphs/job-status/main.js similarity index 86% rename from awx/ui/client/src/dashboard/graphs/job-status/main.js rename to awx/ui/client/src/home/dashboard/graphs/job-status/main.js index 66a6086723..927bc475df 100644 --- a/awx/ui/client/src/dashboard/graphs/job-status/main.js +++ b/awx/ui/client/src/home/dashboard/graphs/job-status/main.js @@ -1,7 +1,7 @@ import JobStatusGraphDirective from './job-status-graph.directive'; import JobStatusGraphService from './job-status-graph.service'; import DashboardGraphHelpers from '../graph-helpers/main'; -import templateUrl from '../../../shared/template-url/main'; +import templateUrl from '../../../../shared/template-url/main'; export default angular.module('JobStatusGraph', [DashboardGraphHelpers.name, templateUrl.name]) .directive('jobStatusGraph', JobStatusGraphDirective) diff --git a/awx/ui/client/src/dashboard/graphs/main.js b/awx/ui/client/src/home/dashboard/graphs/main.js similarity index 100% rename from awx/ui/client/src/dashboard/graphs/main.js rename to awx/ui/client/src/home/dashboard/graphs/main.js diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts-edit.controller.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-edit.controller.js similarity index 100% rename from awx/ui/client/src/dashboard/hosts/dashboard-hosts-edit.controller.js rename to awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-edit.controller.js diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts-list.controller.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-list.controller.js similarity index 100% rename from awx/ui/client/src/dashboard/hosts/dashboard-hosts-list.controller.js rename to awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-list.controller.js diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts.form.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.form.js similarity index 100% rename from awx/ui/client/src/dashboard/hosts/dashboard-hosts.form.js rename to awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.form.js diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts.list.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.list.js similarity index 100% rename from awx/ui/client/src/dashboard/hosts/dashboard-hosts.list.js rename to awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.list.js diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts.service.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.service.js similarity index 100% rename from awx/ui/client/src/dashboard/hosts/dashboard-hosts.service.js rename to awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.service.js diff --git a/awx/ui/client/src/dashboard/hosts/main.js b/awx/ui/client/src/home/dashboard/hosts/main.js similarity index 98% rename from awx/ui/client/src/dashboard/hosts/main.js rename to awx/ui/client/src/home/dashboard/hosts/main.js index 84d04fa8ad..e68efc7300 100644 --- a/awx/ui/client/src/dashboard/hosts/main.js +++ b/awx/ui/client/src/home/dashboard/hosts/main.js @@ -9,7 +9,7 @@ import form from './dashboard-hosts.form'; import listController from './dashboard-hosts-list.controller'; import editController from './dashboard-hosts-edit.controller'; import service from './dashboard-hosts.service'; -import { N_ } from '../../i18n'; +import { N_ } from '../../../i18n'; export default angular.module('dashboardHosts', []) diff --git a/awx/ui/client/src/dashboard/lists/dashboard-list.block.less b/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less similarity index 98% rename from awx/ui/client/src/dashboard/lists/dashboard-list.block.less rename to awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less index 0903b01c74..7937e0906c 100644 --- a/awx/ui/client/src/dashboard/lists/dashboard-list.block.less +++ b/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less @@ -1,6 +1,6 @@ /** @define DashboardList */ -@import "../../shared/branding/colors.default.less"; +@import "../../../shared/branding/colors.default.less"; .DashboardList { flex: 1; diff --git a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js b/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.directive.js similarity index 97% rename from awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js rename to awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.directive.js index 5d4a8a11fd..cfed3878f7 100644 --- a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js +++ b/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.directive.js @@ -11,7 +11,7 @@ export default scope: { data: '=' }, - templateUrl: templateUrl('dashboard/lists/job-templates/job-templates-list') + templateUrl: templateUrl('home/dashboard/lists/job-templates/job-templates-list') }; function link(scope, element, attr) { diff --git a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.partial.html b/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html similarity index 100% rename from awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.partial.html rename to awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html diff --git a/awx/ui/client/src/dashboard/lists/job-templates/main.js b/awx/ui/client/src/home/dashboard/lists/job-templates/main.js similarity index 64% rename from awx/ui/client/src/dashboard/lists/job-templates/main.js rename to awx/ui/client/src/home/dashboard/lists/job-templates/main.js index 90c7f1deb5..1a47bb600b 100644 --- a/awx/ui/client/src/dashboard/lists/job-templates/main.js +++ b/awx/ui/client/src/home/dashboard/lists/job-templates/main.js @@ -1,6 +1,6 @@ import JobTemplatesListDirective from './job-templates-list.directive'; -import systemStatus from '../../../smart-status/main'; -import jobSubmissionHelper from '../../../helpers/JobSubmission'; +import systemStatus from '../../../../smart-status/main'; +import jobSubmissionHelper from '../../../../helpers/JobSubmission'; export default angular.module('JobTemplatesList', [systemStatus.name, jobSubmissionHelper.name]) .directive('jobTemplatesList', JobTemplatesListDirective); diff --git a/awx/ui/client/src/dashboard/lists/jobs/jobs-list.directive.js b/awx/ui/client/src/home/dashboard/lists/jobs/jobs-list.directive.js similarity index 94% rename from awx/ui/client/src/dashboard/lists/jobs/jobs-list.directive.js rename to awx/ui/client/src/home/dashboard/lists/jobs/jobs-list.directive.js index 4658b45574..fd59f4496e 100644 --- a/awx/ui/client/src/dashboard/lists/jobs/jobs-list.directive.js +++ b/awx/ui/client/src/home/dashboard/lists/jobs/jobs-list.directive.js @@ -10,7 +10,7 @@ export default scope: { data: '=' }, - templateUrl: templateUrl('dashboard/lists/jobs/jobs-list') + templateUrl: templateUrl('home/dashboard/lists/jobs/jobs-list') }; function link(scope, element, attr) { diff --git a/awx/ui/client/src/dashboard/lists/jobs/jobs-list.partial.html b/awx/ui/client/src/home/dashboard/lists/jobs/jobs-list.partial.html similarity index 100% rename from awx/ui/client/src/dashboard/lists/jobs/jobs-list.partial.html rename to awx/ui/client/src/home/dashboard/lists/jobs/jobs-list.partial.html diff --git a/awx/ui/client/src/dashboard/lists/jobs/main.js b/awx/ui/client/src/home/dashboard/lists/jobs/main.js similarity index 100% rename from awx/ui/client/src/dashboard/lists/jobs/main.js rename to awx/ui/client/src/home/dashboard/lists/jobs/main.js diff --git a/awx/ui/client/src/dashboard/lists/main.js b/awx/ui/client/src/home/dashboard/lists/main.js similarity index 100% rename from awx/ui/client/src/dashboard/lists/main.js rename to awx/ui/client/src/home/dashboard/lists/main.js diff --git a/awx/ui/client/src/dashboard/main.js b/awx/ui/client/src/home/dashboard/main.js similarity index 100% rename from awx/ui/client/src/dashboard/main.js rename to awx/ui/client/src/home/dashboard/main.js diff --git a/awx/ui/client/src/home/home.controller.js b/awx/ui/client/src/home/home.controller.js new file mode 100644 index 0000000000..b5bf73e5de --- /dev/null +++ b/awx/ui/client/src/home/home.controller.js @@ -0,0 +1,125 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$compile', '$stateParams', '$rootScope', '$location', '$log','Wait', + 'ClearScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$window', 'graphData', + function($scope, $compile, $stateParams, $rootScope, $location, $log, Wait, + ClearScope, Rest, GetBasePath, ProcessErrors, $window, graphData) { + + ClearScope('home'); + + var dataCount = 0; + + $scope.$on('ws-jobs', function () { + Rest.setUrl(GetBasePath('dashboard')); + Rest.get() + .success(function (data) { + $scope.dashboardData = data; + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard host graph data: ' + status }); + }); + + Rest.setUrl(GetBasePath("jobs") + "?order_by=-finished&page_size=5&finished__isnull=false"); + Rest.get() + .success(function (data) { + $scope.dashboardJobsListData = data.results; + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); + }); + + Rest.setUrl(GetBasePath("unified_job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false&type=workflow_job_template,job_template"); + Rest.get() + .success(function (data) { + $scope.dashboardJobTemplatesListData = data.results; + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); + }); + + }); + + if ($scope.removeDashboardDataLoadComplete) { + $scope.removeDashboardDataLoadComplete(); + } + $scope.removeDashboardDataLoadComplete = $scope.$on('dashboardDataLoadComplete', function () { + dataCount++; + if (dataCount === 3) { + Wait("stop"); + dataCount = 0; + } + }); + + if ($scope.removeDashboardReady) { + $scope.removeDashboardReady(); + } + $scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) { + $scope.dashboardCountsData = data; + $scope.graphData = graphData; + $scope.$emit('dashboardDataLoadComplete'); + + var cleanupJobListener = + $rootScope.$on('DataReceived:JobStatusGraph', function(e, data) { + $scope.graphData.jobStatus = data; + }); + + $scope.$on('$destroy', function() { + cleanupJobListener(); + }); + }); + + if ($scope.removeDashboardJobsListReady) { + $scope.removeDashboardJobsListReady(); + } + $scope.removeDashboardJobsListReady = $scope.$on('dashboardJobsListReady', function (e, data) { + $scope.dashboardJobsListData = data; + $scope.$emit('dashboardDataLoadComplete'); + }); + + if ($scope.removeDashboardJobTemplatesListReady) { + $scope.removeDashboardJobTemplatesListReady(); + } + $scope.removeDashboardJobTemplatesListReady = $scope.$on('dashboardJobTemplatesListReady', function (e, data) { + $scope.dashboardJobTemplatesListData = data; + $scope.$emit('dashboardDataLoadComplete'); + }); + + $scope.refresh = function () { + Wait('start'); + Rest.setUrl(GetBasePath('dashboard')); + Rest.get() + .success(function (data) { + $scope.dashboardData = data; + $scope.$emit('dashboardReady', data); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); + }); + Rest.setUrl(GetBasePath("jobs") + "?order_by=-finished&page_size=5&finished__isnull=false"); + Rest.get() + .success(function (data) { + data = data.results; + $scope.$emit('dashboardJobsListReady', data); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); + }); + Rest.setUrl(GetBasePath("unified_job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false&type=workflow_job_template,job_template"); + Rest.get() + .success(function (data) { + data = data.results; + $scope.$emit('dashboardJobTemplatesListReady', data); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard job templates list: ' + status }); + }); + }; + + $scope.refresh(); + + } +]; diff --git a/awx/ui/client/src/partials/home.html b/awx/ui/client/src/home/home.partial.html similarity index 99% rename from awx/ui/client/src/partials/home.html rename to awx/ui/client/src/home/home.partial.html index 4069ec9c7b..6a45b63334 100644 --- a/awx/ui/client/src/partials/home.html +++ b/awx/ui/client/src/home/home.partial.html @@ -1,4 +1,3 @@ -
diff --git a/awx/ui/client/src/home/home.route.js b/awx/ui/client/src/home/home.route.js new file mode 100644 index 0000000000..6c06e510e1 --- /dev/null +++ b/awx/ui/client/src/home/home.route.js @@ -0,0 +1,46 @@ +import {templateUrl} from '../shared/template-url/template-url.factory'; +import controller from './home.controller'; +import { N_ } from '../i18n'; + +export default { + name: 'dashboard', + url: '/home', + templateUrl: templateUrl('home/home'), + controller: controller, + 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"), + }); + }); + } + ] + } + // name: 'setup.about', + // route: '/about', + // controller: controller, + // ncyBreadcrumb: { + // label: N_("ABOUT") + // }, + // onExit: function(){ + // // hacky way to handle user browsing away via URL bar + // $('.modal-backdrop').remove(); + // $('body').removeClass('modal-open'); + // }, + // templateUrl: templateUrl('about/about') +}; diff --git a/awx/ui/client/src/home/main.js b/awx/ui/client/src/home/main.js new file mode 100644 index 0000000000..cc4c6ebb19 --- /dev/null +++ b/awx/ui/client/src/home/main.js @@ -0,0 +1,12 @@ +import dashboard from './dashboard/main'; +import HomeController from './home.controller'; +import route from './home.route'; + +export default + angular.module('home', [ + dashboard.name + ]) + .controller('HomeController', HomeController) + .run(['$stateExtender', function($stateExtender){ + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/projects/add/projects-add.controller.js b/awx/ui/client/src/projects/add/projects-add.controller.js new file mode 100644 index 0000000000..4e513acb13 --- /dev/null +++ b/awx/ui/client/src/projects/add/projects-add.controller.js @@ -0,0 +1,154 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$compile', '$location', '$log', + '$stateParams', 'GenerateForm', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', + 'GetProjectPath', 'GetChoices', 'Wait', '$state', 'CreateSelect2', 'i18n', + function($scope, $rootScope, $compile, $location, $log, + $stateParams, GenerateForm, ProjectsForm, Rest, Alert, ProcessErrors, + GetBasePath, GetProjectPath, GetChoices, Wait, $state, CreateSelect2, i18n) { + + var form = ProjectsForm(), + base = $location.path().replace(/^\//, '').split('/')[0], + defaultUrl = GetBasePath('projects'), + master = {}; + + init(); + + function init() { + Rest.setUrl(GetBasePath('projects')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a project.'), 'alert-info'); + } + }); + + // apply form definition's default field values + GenerateForm.applyDefaults(form, $scope); + } + + GetProjectPath({ scope: $scope, master: master }); + + if ($scope.removeChoicesReady) { + $scope.removeChoicesReady(); + } + $scope.removeChoicesReady = $scope.$on('choicesReady', function() { + var 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"; + //$scope.scm_type = $scope.scm_type_options[i]; + break; + } + } + + CreateSelect2({ + element: '#project_scm_type', + multiple: false + }); + + $scope.scmRequired = false; + master.scm_type = $scope.scm_type; + }); + + // Load the list of options for Kind + GetChoices({ + scope: $scope, + url: defaultUrl, + field: 'scm_type', + variable: 'scm_type_options', + callback: 'choicesReady' + }); + CreateSelect2({ + element: '#local-path-select', + multiple: false + }); + + // Save + $scope.formSave = function() { + var i, fld, url, data = {}; + data = {}; + for (fld in form.fields) { + if (form.fields[fld].type === 'checkbox_group') { + for (i = 0; i < form.fields[fld].fields.length; i++) { + data[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name]; + } + } else { + if (form.fields[fld].type !== 'alertblock') { + data[fld] = $scope[fld]; + } + } + } + + if ($scope.scm_type.value === "manual") { + data.scm_type = ""; + data.local_path = $scope.local_path.value; + } else { + data.scm_type = $scope.scm_type.value; + delete data.local_path; + } + + url = (base === 'teams') ? GetBasePath('teams') + $stateParams.team_id + '/projects/' : defaultUrl; + Wait('start'); + Rest.setUrl(url); + Rest.post(data) + .success(function(data) { + $scope.addedItem = data.id; + $state.go('projects.edit', { project_id: data.id }, { reload: true }); + }) + .error(function(data, status) { + Wait('stop'); + ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), + msg: i18n._('Failed to create new project. POST returned status: ') + status }); + }); + }; + + $scope.scmChange = function() { + // When an scm_type is set, path is not required + if ($scope.scm_type) { + $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'; + } + + // Dynamically update popover values + if ($scope.scm_type.value) { + switch ($scope.scm_type.value) { + case 'git': + $scope.urlPopover = '

' + + i18n._('Example URLs for GIT SCM include:') + + '

  • https://github.com/ansible/ansible.git
  • ' + + '
  • git@github.com:ansible/ansible.git
  • git://servername.example.com/ansible.git
' + + '

' + 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:') + '

' + + '
  • https://github.com/ansible/ansible
  • svn://servername.example.com/path
  • ' + + '
  • svn+ssh://servername.example.com/path
'; + break; + case 'hg': + $scope.urlPopover = '

' + i18n._('Example URLs for Mercurial SCM include:') + '

' + + '
  • https://bitbucket.org/username/project
  • ssh://hg@bitbucket.org/username/project
  • ' + + '
  • ssh://server.example.com/path
' + + '

' + 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.sprintf(i18n._('Are you sure you want to remove the %s below from %s?'), title, $scope.name) + '
' + '
' + name + '
', + action: action, + actionText: i18n._('DELETE') + }); + }; + + $scope.scmChange = function() { + if ($scope.scm_type) { + $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') ? i18n._('Revision #') : i18n._('SCM Branch'); + } + + // Dynamically update popover values + if ($scope.scm_type.value) { + switch ($scope.scm_type.value) { + case 'git': + $scope.urlPopover = '

' + i18n._('Example URLs for GIT SCM include:') + '

  • https://github.com/ansible/ansible.git
  • ' + + '
  • git@github.com:ansible/ansible.git
  • git://servername.example.com/ansible.git
' + + '

' + 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:') + '

' + + '
  • https://github.com/ansible/ansible
  • svn://servername.example.com/path
  • ' + + '
  • svn+ssh://servername.example.com/path
'; + break; + case 'hg': + $scope.urlPopover = '

' + i18n._('Example URLs for Mercurial SCM include:') + '

' + + '
  • https://bitbucket.org/username/project
  • ssh://hg@bitbucket.org/username/project
  • ' + + '
  • ssh://server.example.com/path
' + + '

' + 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: '

' + i18n._('Are you sure you want to delete the project below?') + '
' + '
' + $filter('sanitize')(name) + '
', + action: action, + actionText: 'DELETE' + }); + }; + + if ($scope.removeCancelUpdate) { + $scope.removeCancelUpdate(); + } + $scope.removeCancelUpdate = $scope.$on('Cancel_Update', function(e, url) { + // Cancel the project update process + Rest.setUrl(url); + Rest.post() + .success(function () { + Alert(i18n._('SCM Update Cancel'), i18n._('Your request to cancel the update was submitted to the task manager.'), 'alert-info'); + $scope.refresh(); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. POST status: '), url) + status }); + }); + }); + + if ($scope.removeCheckCancel) { + $scope.removeCheckCancel(); + } + $scope.removeCheckCancel = $scope.$on('Check_Cancel', function(e, data) { + // Check that we 'can' cancel the update + var url = data.related.cancel; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + if (data.can_cancel) { + $scope.$emit('Cancel_Update', url); + } else { + Alert(i18n._('Cancel Not Allowed'), '
' + i18n.sprintf(i18n._('Either you do not have access or the SCM update process completed. ' + + 'Click the %sRefresh%s button to view the latest status.'), '', '') + '
', 'alert-info', null, null, null, null, true); + } + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. GET status: '), url) + status }); + }); + }); + + $scope.cancelUpdate = function(id, name) { + Rest.setUrl(GetBasePath("projects") + id); + Rest.get() + .success(function(data) { + if (data.related.current_update) { + Rest.setUrl(data.related.current_update); + Rest.get() + .success(function(data) { + $scope.$emit('Check_Cancel', data); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), + msg: i18n.sprintf(i18n._('Call to %s failed. GET status: '), data.related.current_update) + status }); + }); + } else { + Alert(i18n._('Update Not Found'), '
' + i18n.sprintf(i18n._('An SCM update does not appear to be running for project: %s. Click the %sRefresh%s ' + + 'button to view the latest status.'), $filter('sanitize')(name), '', '') + '
', 'alert-info',undefined,undefined,undefined,undefined,true); + } + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), + msg: i18n._('Call to get project failed. GET status: ') + status }); + }); + }; + + $scope.SCMUpdate = function(project_id, event) { + try { + $(event.target).tooltip('hide'); + } catch (e) { + // ignore + } + $scope.projects.every(function(project) { + if (project.id === project_id) { + if (project.scm_type === "Manual" || Empty(project.scm_type)) { + // Do not respond. Button appears greyed out as if it is disabled. Not disabled though, because we need mouse over event + // to work. So user can click, but we just won't do anything. + //Alert('Missing SCM Setup', 'Before running an SCM update, edit the project and provide the SCM access information.', 'alert-info'); + } else if (project.status === 'updating' || project.status === 'running' || project.status === 'pending') { + // Alert('Update in Progress', 'The SCM update process is running. Use the Refresh button to monitor the status.', 'alert-info'); + } else { + ProjectUpdate({ scope: $scope, project_id: project.id }); + } + return false; + } + return true; + }); + }; + + $scope.editSchedules = function(id) { + var project = Find({ list: $scope.projects, key: 'id', val: id }); + if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) { + $state.go('projectSchedules', { id: id }); + } + }; + } +]; diff --git a/awx/ui/client/src/projects/main.js b/awx/ui/client/src/projects/main.js new file mode 100644 index 0000000000..05addaf1fe --- /dev/null +++ b/awx/ui/client/src/projects/main.js @@ -0,0 +1,51 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import ProjectsList from './list/projects-list.controller'; +import ProjectsAdd from './add/projects-add.controller'; +import ProjectsEdit from './edit/projects-edit.controller'; +import { N_ } from '../i18n'; + +export default +angular.module('Projects', []) + .controller('ProjectsList', ProjectsList) + .controller('ProjectsAdd', ProjectsAdd) + .controller('ProjectsEdit', ProjectsEdit) + .config(['$stateProvider', 'stateDefinitionsProvider', + function($stateProvider, stateDefinitionsProvider) { + let stateDefinitions = stateDefinitionsProvider.$get(); + + // 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') + } + }) + }); + } + ]); diff --git a/awx/ui/client/src/teams/add/teams-add.controller.js b/awx/ui/client/src/teams/add/teams-add.controller.js new file mode 100644 index 0000000000..7f09f166a7 --- /dev/null +++ b/awx/ui/client/src/teams/add/teams-add.controller.js @@ -0,0 +1,68 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'GenerateForm', + 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state', + function($scope, $rootScope, $stateParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors, + ClearScope, GetBasePath, Wait, $state) { + + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //$scope. + + Rest.setUrl(GetBasePath('teams')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add a team.', 'alert-info'); + } + }); + + // Inject dynamic view + var defaultUrl = GetBasePath('teams'), + form = TeamForm; + + init(); + + function init() { + // apply form definition's default field values + GenerateForm.applyDefaults(form, $scope); + + $rootScope.flashMessage = null; + } + + // Save + $scope.formSave = function() { + var fld, data; + GenerateForm.clearApiErrors($scope); + Wait('start'); + Rest.setUrl(defaultUrl); + data = {}; + for (fld in form.fields) { + data[fld] = $scope[fld]; + } + Rest.post(data) + .success(function(data) { + Wait('stop'); + $rootScope.flashMessage = "New team successfully created!"; + $rootScope.$broadcast("EditIndicatorChange", "users", data.id); + $state.go('teams.edit', { team_id: data.id }, { reload: true }); + }) + .error(function(data, status) { + Wait('stop'); + ProcessErrors($scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to add new team. Post returned status: ' + + status + }); + }); + }; + + $scope.formCancel = function() { + $state.go('teams'); + }; + } +]; diff --git a/awx/ui/client/src/teams/edit/teams-edit.controller.js b/awx/ui/client/src/teams/edit/teams-edit.controller.js new file mode 100644 index 0000000000..c4b1d1fa4c --- /dev/null +++ b/awx/ui/client/src/teams/edit/teams-edit.controller.js @@ -0,0 +1,97 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'Rest', + 'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state', + function($scope, $rootScope, $stateParams, + TeamForm, Rest, ProcessErrors, ClearScope, GetBasePath, Wait, $state) { + + ClearScope(); + + var form = TeamForm, + id = $stateParams.team_id, + defaultUrl = GetBasePath('teams') + id; + + init(); + + function init() { + $scope.team_id = id; + Rest.setUrl(defaultUrl); + Wait('start'); + Rest.get(defaultUrl).success(function(data) { + setScopeFields(data); + $scope.organization_name = data.summary_fields.organization.name; + + $scope.team_obj = data; + Wait('stop'); + }); + + $scope.$watch('team_obj.summary_fields.user_capabilities.edit', function(val) { + if (val === false) { + $scope.canAdd = false; + } + }); + + + } + + // @issue I think all this really want to do is _.forEach(form.fields, (field) =>{ $scope[field] = data[field]}) + function setScopeFields(data) { + _(data) + .pick(function(value, key) { + return form.fields.hasOwnProperty(key) === true; + }) + .forEach(function(value, key) { + $scope[key] = value; + }) + .value(); + return; + } + + // prepares a data payload for a PUT request to the API + function processNewData(fields) { + var data = {}; + _.forEach(fields, function(value, key) { + if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) { + data[key] = $scope[key]; + } + }); + return data; + } + + $scope.formCancel = function() { + $state.go('teams', null, { reload: true }); + }; + + $scope.formSave = function() { + $rootScope.flashMessage = null; + if ($scope[form.name + '_form'].$valid) { + var data = processNewData(form.fields); + Rest.setUrl(defaultUrl); + Rest.put(data).success(function() { + $state.go($state.current, null, { reload: true }); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { + hdr: 'Error!', + msg: 'Failed to retrieve user: ' + + $stateParams.id + '. GET status: ' + status + }); + }); + } + }; + + init(); + + $scope.convertApiUrl = function(str) { + if (str) { + return str.replace("api/v1", "#"); + } else { + return null; + } + }; + } +]; diff --git a/awx/ui/client/src/teams/list/teams-list.controller.js b/awx/ui/client/src/teams/list/teams-list.controller.js new file mode 100644 index 0000000000..a4793de0ec --- /dev/null +++ b/awx/ui/client/src/teams/list/teams-list.controller.js @@ -0,0 +1,81 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', '$rootScope', '$log', + '$stateParams', 'Rest', 'Alert', 'TeamList', 'Prompt', 'ClearScope', + 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', + function($scope, $rootScope, $log, $stateParams, + Rest, Alert, TeamList, Prompt, ClearScope, ProcessErrors, + GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) { + + ClearScope(); + + var list = TeamList, + defaultUrl = GetBasePath('teams'); + + init(); + + function init() { + $scope.canAdd = false; + + rbacUiControlService.canAdd('teams') + .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], (team) => { + team.organization_name = team.summary_fields.organization.name; + }); + + $scope.selected = []; + } + + $scope.addTeam = function() { + $state.go('teams.add'); + }; + + $scope.editTeam = function(id) { + $state.go('teams.edit', { team_id: id }); + }; + + $scope.deleteTeam = function(id, name) { + + var action = function() { + Wait('start'); + var url = defaultUrl + id + '/'; + Rest.setUrl(url); + Rest.destroy() + .success(function() { + Wait('stop'); + $('#prompt-modal').modal('hide'); + if (parseInt($state.params.team_id) === id) { + $state.go('^', null, { reload: true }); + } else { + $state.go('.', null, { reload: true }); + } + }) + .error(function(data, status) { + Wait('stop'); + $('#prompt-modal').modal('hide'); + ProcessErrors($scope, data, status, null, { + hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status + }); + }); + }; + + Prompt({ + hdr: 'Delete', + body: '
Are you sure you want to delete the team below?
' + $filter('sanitize')(name) + '
', + action: action, + actionText: 'DELETE' + }); + }; + } +]; diff --git a/awx/ui/client/src/teams/main.js b/awx/ui/client/src/teams/main.js new file mode 100644 index 0000000000..cd800ddb11 --- /dev/null +++ b/awx/ui/client/src/teams/main.js @@ -0,0 +1,47 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import TeamsList from './list/teams-list.controller'; +import TeamsAdd from './add/teams-add.controller'; +import TeamsEdit from './edit/teams-edit.controller'; +import { N_ } from '../i18n'; + +export default +angular.module('Teams', []) + .controller('TeamsList', TeamsList) + .controller('TeamsAdd', TeamsAdd) + .controller('TeamsEdit', TeamsEdit) + .config(['$stateProvider', 'stateDefinitionsProvider', + function($stateProvider, stateDefinitionsProvider) { + let stateDefinitions = stateDefinitionsProvider.$get(); + + // 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: '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') + } + }) + }); + } + ]); diff --git a/awx/ui/client/src/users/add/users-add.controller.js b/awx/ui/client/src/users/add/users-add.controller.js new file mode 100644 index 0000000000..df174a0da6 --- /dev/null +++ b/awx/ui/client/src/users/add/users-add.controller.js @@ -0,0 +1,119 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import { N_ } from "../../i18n"; + +const user_type_options = [ + { type: 'normal', label: N_('Normal User') }, + { type: 'system_auditor', label: N_('System Auditor') }, + { type: 'system_administrator', label: N_('System Administrator') }, +]; + +export default ['$scope', '$rootScope', '$stateParams', 'UserForm', 'GenerateForm', + 'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath', + 'ResetForm', 'Wait', 'CreateSelect2', '$state', '$location', 'i18n', + function($scope, $rootScope, $stateParams, UserForm, + GenerateForm, Rest, Alert, ProcessErrors, ReturnToCaller, ClearScope, + GetBasePath, ResetForm, Wait, CreateSelect2, $state, $location, i18n) { + + ClearScope(); + + var defaultUrl = GetBasePath('organizations'), + form = UserForm; + + init(); + + function init() { + // apply form definition's default field values + GenerateForm.applyDefaults(form, $scope); + + $scope.ldap_user = false; + $scope.not_ldap_user = !$scope.ldap_user; + $scope.ldap_dn = null; + $scope.socialAuthUser = false; + $scope.external_account = null; + + Rest.setUrl(GetBasePath('users')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a user.'), 'alert-info'); + } + }); + + $scope.user_type_options = user_type_options; + $scope.user_type = user_type_options[0]; + $scope.$watch('user_type', user_type_sync($scope)); + CreateSelect2({ + element: '#user_user_type', + multiple: false + }); + } + + function user_type_sync($scope) { + return (type_option) => { + $scope.is_superuser = false; + $scope.is_system_auditor = false; + switch (type_option.type) { + case 'system_administrator': + $scope.is_superuser = true; + break; + case 'system_auditor': + $scope.is_system_auditor = true; + break; + } + }; + } + + // Save + $scope.formSave = function() { + var fld, data = {}; + if ($scope[form.name + '_form'].$valid) { + if ($scope.organization !== undefined && $scope.organization !== null && $scope.organization !== '') { + Rest.setUrl(defaultUrl + $scope.organization + '/users/'); + for (fld in form.fields) { + if (form.fields[fld].realName) { + data[form.fields[fld].realName] = $scope[fld]; + } else { + data[fld] = $scope[fld]; + } + } + data.is_superuser = $scope.is_superuser; + data.is_system_auditor = $scope.is_system_auditor; + Wait('start'); + Rest.post(data) + .success(function(data) { + var base = $location.path().replace(/^\//, '').split('/')[0]; + if (base === 'users') { + $rootScope.flashMessage = i18n._('New user successfully created!'); + $rootScope.$broadcast("EditIndicatorChange", "users", data.id); + $state.go('users.edit', { user_id: data.id }, { reload: true }); + } else { + ReturnToCaller(1); + } + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n._('Failed to add new user. POST returned status: ') + status }); + }); + } else { + $scope.organization_name_api_error = i18n._('A value is required'); + } + } + }; + + $scope.formCancel = function() { + $state.go('users'); + }; + + // Password change + $scope.clearPWConfirm = function(fld) { + // If password value changes, make sure password_confirm must be re-entered + $scope[fld] = ''; + $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); + }; + } +]; diff --git a/awx/ui/client/src/users/edit/users-edit.controller.js b/awx/ui/client/src/users/edit/users-edit.controller.js new file mode 100644 index 0000000000..545670117e --- /dev/null +++ b/awx/ui/client/src/users/edit/users-edit.controller.js @@ -0,0 +1,179 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import { N_ } from "../../i18n"; + +const user_type_options = [ + { type: 'normal', label: N_('Normal User') }, + { type: 'system_auditor', label: N_('System Auditor') }, + { type: 'system_administrator', label: N_('System Administrator') }, +]; + +export default ['$scope', '$rootScope', '$location', + '$stateParams', 'UserForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath', + 'ResetForm', 'Wait', 'CreateSelect2', '$state', 'i18n', + function($scope, $rootScope, $location, + $stateParams, UserForm, Rest, ProcessErrors, + ClearScope, GetBasePath, ResetForm, Wait, CreateSelect2, $state, i18n) { + + for (var i = 0; i < user_type_options.length; i++) { + user_type_options[i].label = i18n._(user_type_options[i].label); + } + ClearScope(); + + var form = UserForm, + master = {}, + id = $stateParams.user_id, + defaultUrl = GetBasePath('users') + id; + + init(); + + function init() { + $scope.hidePagination = false; + $scope.hideSmartSearch = false; + $scope.user_type_options = user_type_options; + $scope.user_type = user_type_options[0]; + $scope.$watch('user_type', user_type_sync($scope)); + $scope.$watch('is_superuser', hidePermissionsTabSmartSearchAndPaginationIfSuperUser($scope)); + Rest.setUrl(defaultUrl); + Wait('start'); + Rest.get(defaultUrl).success(function(data) { + $scope.user_id = id; + $scope.ldap_user = (data.ldap_dn !== null && data.ldap_dn !== undefined && data.ldap_dn !== '') ? true : false; + $scope.not_ldap_user = !$scope.ldap_user; + master.ldap_user = $scope.ldap_user; + $scope.socialAuthUser = (data.auth.length > 0) ? true : false; + $scope.external_account = data.external_account; + + $scope.user_type = $scope.user_type_options[0]; + $scope.is_system_auditor = false; + $scope.is_superuser = false; + if (data.is_system_auditor) { + $scope.user_type = $scope.user_type_options[1]; + $scope.is_system_auditor = true; + } + if (data.is_superuser) { + $scope.user_type = $scope.user_type_options[2]; + $scope.is_superuser = true; + } + + $scope.user_obj = data; + $scope.name = data.username; + + CreateSelect2({ + element: '#user_user_type', + multiple: false + }); + + $scope.$watch('user_obj.summary_fields.user_capabilities.edit', function(val) { + if (val === false) { + $scope.canAdd = false; + } + }); + + setScopeFields(data); + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { + hdr: i18n._('Error!'), + msg: i18n.sprintf(i18n._('Failed to retrieve user: %s. GET status: '), $stateParams.id) + status + }); + }); + } + + function user_type_sync($scope) { + return (type_option) => { + $scope.is_superuser = false; + $scope.is_system_auditor = false; + switch (type_option.type) { + case 'system_administrator': + $scope.is_superuser = true; + break; + case 'system_auditor': + $scope.is_system_auditor = true; + break; + } + }; + } + + // Organizations and Teams tab pagination is hidden through other mechanism + function hidePermissionsTabSmartSearchAndPaginationIfSuperUser(scope) { + return function(isSuperuserNewValue) { + let shouldHide = isSuperuserNewValue; + if (shouldHide === true) { + scope.hidePagination = true; + scope.hideSmartSearch = true; + } else if (shouldHide === false) { + scope.hidePagination = false; + scope.hideSmartSearch = false; + } + }; + } + + + function setScopeFields(data) { + _(data) + .pick(function(value, key) { + return form.fields.hasOwnProperty(key) === true; + }) + .forEach(function(value, key) { + $scope[key] = value; + }) + .value(); + return; + } + + $scope.convertApiUrl = function(str) { + if (str) { + return str.replace("api/v1", "#"); + } else { + return null; + } + }; + + // prepares a data payload for a PUT request to the API + var processNewData = function(fields) { + var data = {}; + _.forEach(fields, function(value, key) { + if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) { + data[key] = $scope[key]; + } + }); + data.is_superuser = $scope.is_superuser; + data.is_system_auditor = $scope.is_system_auditor; + return data; + }; + + $scope.formCancel = function() { + $state.go('users', null, { reload: true }); + }; + + $scope.formSave = function() { + $rootScope.flashMessage = null; + if ($scope[form.name + '_form'].$valid) { + Rest.setUrl(defaultUrl + '/'); + var data = processNewData(form.fields); + Rest.put(data).success(function() { + $state.go($state.current, null, { reload: true }); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { + hdr: i18n._('Error!'), + msg: i18n.sprintf(i18n._('Failed to retrieve user: %s. GET status: '), $stateParams.id) + status + }); + }); + } + }; + + $scope.clearPWConfirm = function(fld) { + // If password value changes, make sure password_confirm must be re-entered + $scope[fld] = ''; + $scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); + $rootScope.flashMessage = null; + }; + } +]; diff --git a/awx/ui/client/src/users/list/users-list.controller.js b/awx/ui/client/src/users/list/users-list.controller.js new file mode 100644 index 0000000000..437690da83 --- /dev/null +++ b/awx/ui/client/src/users/list/users-list.controller.js @@ -0,0 +1,90 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import { N_ } from "../../i18n"; + +const user_type_options = [ + { type: 'normal', label: N_('Normal User') }, + { type: 'system_auditor', label: N_('System Auditor') }, + { type: 'system_administrator', label: N_('System Administrator') }, +]; + +export default ['$scope', '$rootScope', '$stateParams', + 'Rest', 'Alert', 'UserList', 'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath', + 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n', + function($scope, $rootScope, $stateParams, + Rest, Alert, UserList, Prompt, ClearScope, ProcessErrors, GetBasePath, + Wait, $state, $filter, rbacUiControlService, Dataset, i18n) { + + for (var i = 0; i < user_type_options.length; i++) { + user_type_options[i].label = i18n._(user_type_options[i].label); + } + + ClearScope(); + + var list = UserList, + defaultUrl = GetBasePath('users'); + + init(); + + function init() { + $scope.canAdd = false; + + rbacUiControlService.canAdd('users') + .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; + + + $rootScope.flashMessage = null; + $scope.selected = []; + } + + $scope.addUser = function() { + $state.go('users.add'); + }; + + $scope.editUser = function(id) { + $state.go('users.edit', { user_id: id }); + }; + + $scope.deleteUser = 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.user_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 + }); + }); + }; + + Prompt({ + hdr: i18n._('Delete'), + body: '
' + i18n._('Are you sure you want to delete the user below?') + '
' + $filter('sanitize')(name) + '
', + action: action, + actionText: i18n._('DELETE') + }); + }; + } +]; diff --git a/awx/ui/client/src/users/main.js b/awx/ui/client/src/users/main.js new file mode 100644 index 0000000000..1d978a091b --- /dev/null +++ b/awx/ui/client/src/users/main.js @@ -0,0 +1,47 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import UsersList from './list/users-list.controller'; +import UsersAdd from './add/users-add.controller'; +import UsersEdit from './edit/users-edit.controller'; +import { N_ } from '../i18n'; + +export default +angular.module('Users', []) + .controller('UsersList', UsersList) + .controller('UsersAdd', UsersAdd) + .controller('UsersEdit', UsersEdit) + .config(['$stateProvider', 'stateDefinitionsProvider', + function($stateProvider, stateDefinitionsProvider) { + let stateDefinitions = stateDefinitionsProvider.$get(); + + // 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: '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') + } + }) + }); + } + ]); From bfcbd8a82346701e887ccec51b5cf2bbd005eb2f Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Tue, 28 Feb 2017 12:03:33 -0500 Subject: [PATCH 05/39] Leftover cleanup from 3.1 back to devel --- .../src/credentials/list/credentials-list.controller.js | 4 +--- awx/ui/client/src/home/home.controller.js | 4 ++-- .../client/src/projects/list/projects-list.controller.js | 8 ++------ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js index 859e0ea998..6f420f2cd9 100644 --- a/awx/ui/client/src/credentials/list/credentials-list.controller.js +++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js @@ -52,12 +52,10 @@ export default ['$scope', '$rootScope', '$location', '$log', // Set the item type label if (list.fields.kind && $scope.options && $scope.options.hasOwnProperty('kind')) { - $scope.options.kind.choices.every(function(choice) { + $scope.options.kind.choices.forEach(function(choice) { if (choice[0] === item.kind) { itm.kind_label = choice[1]; - return false; } - return true; }); } }); diff --git a/awx/ui/client/src/home/home.controller.js b/awx/ui/client/src/home/home.controller.js index b5bf73e5de..80d4da45f1 100644 --- a/awx/ui/client/src/home/home.controller.js +++ b/awx/ui/client/src/home/home.controller.js @@ -23,7 +23,7 @@ export default ['$scope', '$compile', '$stateParams', '$rootScope', '$location', ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard host graph data: ' + status }); }); - Rest.setUrl(GetBasePath("jobs") + "?order_by=-finished&page_size=5&finished__isnull=false"); + Rest.setUrl("api/v1/unified_jobs?order_by=-finished&page_size=5&finished__isnull=false&type=workflow_job,job"); Rest.get() .success(function (data) { $scope.dashboardJobsListData = data.results; @@ -99,7 +99,7 @@ export default ['$scope', '$compile', '$stateParams', '$rootScope', '$location', .error(function (data, status) { ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); }); - Rest.setUrl(GetBasePath("jobs") + "?order_by=-finished&page_size=5&finished__isnull=false"); + Rest.setUrl("api/v1/unified_jobs?order_by=-finished&page_size=5&finished__isnull=false&type=workflow_job,job"); Rest.get() .success(function (data) { data = data.results; diff --git a/awx/ui/client/src/projects/list/projects-list.controller.js b/awx/ui/client/src/projects/list/projects-list.controller.js index 6ecb5eb7fa..09f53ad019 100644 --- a/awx/ui/client/src/projects/list/projects-list.controller.js +++ b/awx/ui/client/src/projects/list/projects-list.controller.js @@ -55,12 +55,10 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', // 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) { + $scope.options.scm_type.choices.forEach(function(choice) { if (choice[0] === item.scm_type) { itm.type_label = choice[1]; - return false; } - return true; }); } @@ -272,7 +270,7 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', } catch (e) { // ignore } - $scope.projects.every(function(project) { + $scope.projects.forEach(function(project) { if (project.id === project_id) { if (project.scm_type === "Manual" || Empty(project.scm_type)) { // Do not respond. Button appears greyed out as if it is disabled. Not disabled though, because we need mouse over event @@ -283,9 +281,7 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', } else { ProjectUpdate({ scope: $scope, project_id: project.id }); } - return false; } - return true; }); }; From d97ff57cdaa7625abe4417def64df68f5d68c6ec Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Fri, 20 Jan 2017 09:10:19 -0500 Subject: [PATCH 06/39] prohibit API payloads that represent something other than a JSON object The JSON serializer for our API uses ``json.loads``, which permits *any* valid JSON (including bare integers, boolean values, etc). Lots of our code, however, assumes that inbound JSON content will be a dict. see: #4756 --- awx/api/parsers.py | 5 ++++- .../tests/functional/api/test_job_template.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/awx/api/parsers.py b/awx/api/parsers.py index 8c720201a2..826c67189a 100644 --- a/awx/api/parsers.py +++ b/awx/api/parsers.py @@ -26,6 +26,9 @@ class JSONParser(parsers.JSONParser): try: data = stream.read().decode(encoding) - return json.loads(data, object_pairs_hook=OrderedDict) + obj = json.loads(data, object_pairs_hook=OrderedDict) + if not isinstance(obj, dict): + raise ParseError(_('JSON parse error - not a JSON object')) + return obj except ValueError as exc: raise ParseError(_('JSON parse error - %s') % six.text_type(exc)) diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py index ec4286176e..95f0d5d043 100644 --- a/awx/main/tests/functional/api/test_job_template.py +++ b/awx/main/tests/functional/api/test_job_template.py @@ -94,6 +94,23 @@ def test_edit_playbook(patch, job_template_factory, alice): }, alice, expect=403) +@pytest.mark.django_db +@pytest.mark.parametrize('json_body', + ["abc", True, False, "{\"name\": \"test\"}", 100, .5]) +def test_invalid_json_body(patch, job_template_factory, alice, json_body): + objs = job_template_factory('jt', organization='org1') + objs.job_template.admin_role.members.add(alice) + resp = patch( + reverse('api:job_template_detail', args=(objs.job_template.id,)), + json_body, + alice, + expect=400 + ) + assert resp.data['detail'] == ( + u'JSON parse error - not a JSON object' + ) + + @pytest.mark.django_db def test_edit_nonsenstive(patch, job_template_factory, alice): objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred') From cb30b47098a05def48e00d283376eeaca136f5b2 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 28 Feb 2017 15:46:32 -0500 Subject: [PATCH 07/39] remove old search comments --- .../edit/credentials-edit.controller.js | 2 - awx/ui/client/src/helpers/Credentials.js | 9 -- awx/ui/client/src/helpers/Groups.js | 19 --- awx/ui/client/src/helpers/Hosts.js | 59 --------- awx/ui/client/src/helpers/JobDetail.js | 88 ------------- awx/ui/client/src/helpers/teams.js | 3 - .../host-events/host-events.block.less | 6 - .../host-events/host-events.controller.js | 50 +------ .../host-events/host-events.partial.html | 17 +-- .../host-summary/host-summary.controller.js | 23 ---- .../src/job-detail/job-detail.controller.js | 123 ------------------ .../management-jobs/card/card.controller.js | 9 +- .../edit/organizations-edit.controller.js | 3 - .../organizations-projects.controller.js | 15 +-- .../projects/edit/projects-edit.controller.js | 2 - 15 files changed, 8 insertions(+), 420 deletions(-) diff --git a/awx/ui/client/src/credentials/edit/credentials-edit.controller.js b/awx/ui/client/src/credentials/edit/credentials-edit.controller.js index 1926e811ca..2dd3ae1de2 100644 --- a/awx/ui/client/src/credentials/edit/credentials-edit.controller.js +++ b/awx/ui/client/src/credentials/edit/credentials-edit.controller.js @@ -283,8 +283,6 @@ export default ['$scope', '$rootScope', '$compile', '$location', }) .success(function() { $('#prompt-modal').modal('hide'); - // @issue: OLD SEARCH - // $scope.search(form.related[set].iterator); }) .error(function(data, status) { $('#prompt-modal').modal('hide'); diff --git a/awx/ui/client/src/helpers/Credentials.js b/awx/ui/client/src/helpers/Credentials.js index 007756d17c..edb1e700ab 100644 --- a/awx/ui/client/src/helpers/Credentials.js +++ b/awx/ui/client/src/helpers/Credentials.js @@ -386,15 +386,6 @@ angular.module('CredentialsHelper', ['Utilities']) Rest.post(data) .success(function (data) { scope.addedItem = data.id; - - // @issue: OLD SEARCH - // Refresh({ - // scope: scope, - // set: 'credentials', - // iterator: 'credential', - // url: url - // }); - Wait('stop'); var base = $location.path().replace(/^\//, '').split('/')[0]; if (base === 'credentials') { diff --git a/awx/ui/client/src/helpers/Groups.js b/awx/ui/client/src/helpers/Groups.js index 8e7dcfc7ad..295af244a1 100644 --- a/awx/ui/client/src/helpers/Groups.js +++ b/awx/ui/client/src/helpers/Groups.js @@ -838,14 +838,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name parent_scope.removeAddTreeRefreshed(); } parent_scope.removeAddTreeRefreshed = parent_scope.$on('GroupTreeRefreshed', function() { - // Clean up Wait('stop'); - - // @issue: OLD SEARCH - // if (modal_scope.searchCleanUp) { - // modal_scope.searchCleanup(); - // } - try { $('#group-modal-dialog').dialog('close'); } @@ -948,18 +941,6 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name catch(e) { //ignore } - - // @issue: OLD SEARCH - // if (modal_scope.searchCleanup) { - // modal_scope.searchCleanup(); - // } - // if (parent_scope.restoreSearch) { - // parent_scope.restoreSearch(); - // } - // else { - // Wait('stop'); - // } - Wait('stop'); }; diff --git a/awx/ui/client/src/helpers/Hosts.js b/awx/ui/client/src/helpers/Hosts.js index 2cb4aa36a5..627c3a76eb 100644 --- a/awx/ui/client/src/helpers/Hosts.js +++ b/awx/ui/client/src/helpers/Hosts.js @@ -169,18 +169,6 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, var scope = params.scope, parent_scope = params.parent_scope; - // @issue: OLD SEARCH - // var list = InventoryHosts, - // group_id = params.group_id, - // inventory_id = params.inventory_id; - // pageSize = (params.pageSize) ? params.pageSize : 20, - // - // url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' : - // GetBasePath('inventory') + inventory_id + '/hosts/'; - - // @issue: OLD SEARCH - // scope.search_place_holder='Search ' + scope.selected_group_name; - if (scope.removeHostsReloadPostRefresh) { scope.removeHostsReloadPostRefresh(); } @@ -198,27 +186,6 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, } } }); - - // @issue: OLD SEARCH - // SearchInit({ scope: scope, set: 'hosts', list: list, url: url }); - // PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize }); - // - // if ($stateParams.host_name) { - // scope[list.iterator + 'InputDisable'] = false; - // scope[list.iterator + 'SearchValue'] = $stateParams.host_name; - // scope[list.iterator + 'SearchField'] = 'name'; - // scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label; - // scope[list.iterator + 'SearchSelectValue'] = null; - // } - // - // if (scope.show_failures) { - // scope[list.iterator + 'InputDisable'] = true; - // scope[list.iterator + 'SearchValue'] = 'true'; - // scope[list.iterator + 'SearchField'] = 'has_active_failures'; - // scope[list.iterator + 'SearchFieldLabel'] = list.fields.has_active_failures.label; - // scope[list.iterator + 'SearchSelectValue'] = { value: 1 }; - // } - // scope.search(list.iterator, null, true); }; }]) @@ -274,31 +241,11 @@ return function(params) { scope.removeHostCopyDialogReady(); } scope.removeCopyDialogReady = scope.$on('HostCopyDialogReady', function() { - // @issue: OLD SEARCH - // var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/'; - GenerateList.inject(GroupList, { mode: 'lookup', id: 'copy-host-select-container', scope: scope - //, - //instructions: instructions }); - - // @issue: OLD SEARCH - // SearchInit({ - // scope: scope, - // set: GroupList.name, - // list: GroupList, - // url: url - // }); - // PaginateInit({ - // scope: scope, - // list: GroupList, - // url: url, - // mode: 'lookup' - // }); - // scope.search(GroupList.iterator, null, true, false); }); if (scope.removeShowDialog) { @@ -348,12 +295,6 @@ return function(params) { catch(e) { // ignore } - - // @issue: OLD SEARCH - // scope.searchCleanup(); - - // @issue: OLD SEARCH - // group_scope.restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists scope.$destroy(); }; diff --git a/awx/ui/client/src/helpers/JobDetail.js b/awx/ui/client/src/helpers/JobDetail.js index 0cda454f2e..ad049ed481 100644 --- a/awx/ui/client/src/helpers/JobDetail.js +++ b/awx/ui/client/src/helpers/JobDetail.js @@ -686,11 +686,6 @@ export default scope.plays = []; - // @issue: OLD SEARCH - factory needs refactoring! will be completely rehauled for job details 3.1 update - // url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id'; - // url += (scope.search_play_name) ? '&play__icontains=' + encodeURIComponent(scope.search_play_name) : ''; - // url += (scope.search_play_status === 'failed') ? '&failed=true' : ''; - scope.playsLoading = true; Rest.setUrl(url); Rest.get() @@ -789,11 +784,6 @@ export default if (scope.selectedPlay) { url = scope.job.url + 'job_tasks/?event_id=' + scope.selectedPlay; - // @issue: OLD SEARCH - // url += (scope.search_task_name) ? '&task__icontains=' + encodeURIComponent(scope.search_task_name) : ''; - // url += (scope.search_task_status === 'failed') ? '&failed=true' : ''; - // url += '&page_size=' + scope.tasksMaxRows + '&order=id'; - scope.plays.every(function(p, idx) { if (p.id === scope.selectedPlay) { play = scope.plays[idx]; @@ -924,11 +914,6 @@ export default order: 'host_name,counter', }; - // @issue: OLD SEARCH - // if (scope.search_host_status === 'failed'){ - // params.failed = true; - // } - JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){ scope.hostResults = JobDetailService.processHostEvents(res.results); scope.hostResultsLoading = false; @@ -1060,30 +1045,6 @@ export default } } - // @issue: OLD SEARCH - // if (scope.search_play_name) { - // for (key in plays) { - // if (filteredListX[key].name.indexOf(scope.search_play_name) > 0) { - // filteredListA[key] = filteredListX[key]; - // } - // } - // } - // else { - // filteredListA = filteredListX; - // } - - // @issue: OLD SEARCH - // if (scope.search_play_status === 'failed') { - // for (key in filteredListA) { - // if (filteredListA[key].status === 'failed') { - // filteredListB[key] = plays[key]; - // } - // } - // } - // else { - // filteredListB = filteredListA; - // } - keys = Object.keys(filteredListB); keys.sort(function(a,b) { return listSort(a,b); }).reverse(); for (idx=0; idx < scope.playsMaxRows && idx < keys.length; idx++) { @@ -1139,30 +1100,6 @@ export default } } - // @issue: OLD SEARCH - // if (scope.search_task_name) { - // for (key in filteredListX) { - // if (filteredListX[key].name.indexOf(scope.search_task_name) > 0) { - // filteredListA[key] = filteredListX[key]; - // } - // } - // } - // else { - // filteredListA = filteredListX; - // } - - // @issue: OLD SEARCH - // if (scope.search_task_status === 'failed') { - // for (key in filteredListA) { - // if (filteredListA[key].status === 'failed') { - // filteredListB[key] = tasks[key]; - // } - // } - // } - // else { - // filteredListB = filteredListA; - // } - keys = Object.keys(filteredListB); keys.sort(function(a,b) { return listSort(a,b); }).reverse(); newKeys = []; @@ -1202,33 +1139,8 @@ export default if (scope.activePlay && scope.activeTask && scope.jobData.plays[scope.activePlay] && scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) { - //hostResults = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults)); hostResults = scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults; - // @issue: OLD SEARCH - // if (scope.search_host_name) { - // for (key in hostResults) { - // if (hostResults[key].name.indexOf(scope.search_host_name) > 0) { - // filteredListA[key] = hostResults[key]; - // } - // } - // } - // else { - // filteredListA = hostResults; - // } - - // @issue: OLD SEARCH - // if (scope.search_host_status === 'failed' || scope.search_host_status === 'unreachable') { - // for (key in filteredListA) { - // if (filteredListA[key].status === 'failed') { - // filteredListB[key] = filteredListA[key]; - // } - // } - // } - // else { - // filteredListB = filteredListA; - // } - keys = Object.keys(filteredListB); keys.sort(function compare(a, b) { if (filteredListB[a].name === filteredListB[b].name) { diff --git a/awx/ui/client/src/helpers/teams.js b/awx/ui/client/src/helpers/teams.js index b89d2a1283..936023f003 100644 --- a/awx/ui/client/src/helpers/teams.js +++ b/awx/ui/client/src/helpers/teams.js @@ -39,9 +39,6 @@ export default } } - // @issue: OLD SEARCH - // scope[iterator + 'SearchSpin'] = false; - scope[set] = results; } }); diff --git a/awx/ui/client/src/job-detail/host-events/host-events.block.less b/awx/ui/client/src/job-detail/host-events/host-events.block.less index c309dfeab6..df52944cf0 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.block.less +++ b/awx/ui/client/src/job-detail/host-events/host-events.block.less @@ -38,12 +38,6 @@ button.HostEvents-close{ color: @skipped; } -// @issue: OLD SEARCH -// .HostEvents-search--form{ -// max-width: 420px; -// display: inline-block; -// } - .HostEvents-filter--form{ padding-top: 15px; padding-bottom: 15px; diff --git a/awx/ui/client/src/job-detail/host-events/host-events.controller.js b/awx/ui/client/src/job-detail/host-events/host-events.controller.js index 87b1bc00a0..5664c89877 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.controller.js +++ b/awx/ui/client/src/job-detail/host-events/host-events.controller.js @@ -16,56 +16,11 @@ $scope.processEventStatus = JobDetailService.processEventStatus; $scope.activeFilter = $stateParams.filter || null; - // @issue: OLD SEARCH - // $scope.search = function(){ - // Wait('start'); - // //http://docs.ansible.com/ansible-tower/latest/html/towerapi/intro.html#filtering - // // SELECT WHERE host_name LIKE str OR WHERE play LIKE str OR WHERE task LIKE str AND host_name NOT "" - // // selecting non-empty host_name fields prevents us from displaying non-runner events, like playbook_on_task_start - // var params = { - // host_name: $scope.hostName, - // }; - // if ($scope.searchStr && $scope.searchStr !== ''){ - // params.or__play__icontains = encodeURIComponent($scope.searchStr); - // params.or__task__icontains = encodeURIComponent($scope.searchStr); - // } - // - // switch($scope.activeFilter){ - // case 'skipped': - // params.event = 'runner_on_skipped'; - // break; - // case 'unreachable': - // params.event = 'runner_on_unreachable'; - // break; - // case 'ok': - // params.event = 'runner_on_ok'; - // params.changed = 'false'; - // break; - // case 'failed': - // params.event = 'runner_on_failed'; - // break; - // case 'changed': - // params.event = 'runner_on_ok'; - // params.changed = true; - // break; - // default: - // break; - // } - // JobDetailService.getRelatedJobEvents($stateParams.id, params) - // .success(function(res){ - // $scope.results = res.results; - // Wait('stop'); - // }); - // }; - $scope.filters = ['all', 'changed', 'failed', 'ok', 'unreachable', 'skipped']; // watch select2 for changes $('.HostEvents-select').on("select2:select", function () { - $scope.activeFilter = $('.HostEvents-select').val(); - - // @issue: OLD SEARCH - // $scope.search(); + $scope.activeFilter = $('.HostEvents-select').val(); }); var init = function(){ @@ -79,9 +34,6 @@ if ($stateParams.filter){ $scope.activeFilter = $stateParams.filter; - // @issue: OLD SEARCH - // $scope.search(); - $('#HostEvents').modal('show'); } else{ diff --git a/awx/ui/client/src/job-detail/host-events/host-events.partial.html b/awx/ui/client/src/job-detail/host-events/host-events.partial.html index c0b598a23a..00bacf066c 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.partial.html +++ b/awx/ui/client/src/job-detail/host-events/host-events.partial.html @@ -10,20 +10,9 @@
- - - - - +
diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js index 2e3716a945..cd5a241622 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js @@ -95,29 +95,6 @@ } }; - // @issue: OLD SEARCH - // $scope.search = function(){ - // if($scope.searchTerm && $scope.searchTerm !== '') { - // $scope.searchActive = true; - // Wait('start'); - // JobDetailService.getJobHostSummaries($stateParams.id, { - // page_size: page_size, - // host_name__icontains: encodeURIComponent($scope.searchTerm), - // }).success(function(res){ - // $scope.hosts = res.results; - // $scope.next = res.next; - // Wait('stop'); - // }); - // } - // }; - - // @issue: OLD SEARCH - // $scope.clearSearch = function(){ - // $scope.searchActive = false; - // $scope.searchTerm = null; - // init(); - // }; - $scope.setFilter = function(filter){ $scope.filter = filter; var getAll = function(){ diff --git a/awx/ui/client/src/job-detail/job-detail.controller.js b/awx/ui/client/src/job-detail/job-detail.controller.js index 067c26aa1f..c3479e397b 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -149,18 +149,6 @@ export default scope.job_id = job_id; scope.auto_scroll = false; - // @issue: OLD SEARCH - // scope.searchPlaysEnabled = true; - // scope.searchTasksEnabled = true; - // scope.searchHostsEnabled = true; - // scope.search_play_status = 'all'; - // scope.search_task_status = 'all'; - // scope.search_host_status = 'all'; - // - // scope.search_play_name = ''; - // scope.search_task_name = ''; - // scope.search_host_name = ''; - scope.haltEventQueue = false; scope.processing = false; scope.lessStatus = false; @@ -768,117 +756,6 @@ export default } }; - // @issue: OLD SEARCH - // scope.filterTaskStatus = function() { - // scope.search_task_status = (scope.search_task_status === 'all') ? 'failed' : 'all'; - // if (!scope.liveEventProcessing || scope.pauseLiveEvents) { - // LoadTasks({ - // scope: scope - // }); - // } - // }; - // scope.filterPlayStatus = function() { - // scope.search_play_status = (scope.search_play_status === 'all') ? 'failed' : 'all'; - // if (!scope.liveEventProcessing || scope.pauseLiveEvents) { - // LoadPlays({ - // scope: scope - // }); - // } - // }; - // scope.filterHostStatus = function(){ - // scope.search_host_status = (scope.search_host_status === 'all') ? 'failed' : 'all'; - // if (!scope.liveEventProcessing || scope.pauseLiveEvents){ - // if (scope.selectedTask !== null && scope.selectedPlay !== null){ - // var params = { - // parent: scope.selectedTask, - // page_size: scope.hostResultsMaxRows, - // order: 'host_name,counter', - // }; - // if (scope.search_host_status === 'failed'){ - // params.failed = true; - // } - // JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){ - // scope.hostResults = JobDetailService.processHostEvents(res.results); - // scope.hostResultsLoading = false; - // }); - // } - // } - // }; - // scope.searchPlays = function() { - // if (scope.search_play_name) { - // scope.searchPlaysEnabled = false; - // } - // else { - // scope.searchPlaysEnabled = true; - // } - // if (!scope.liveEventProcessing || scope.pauseLiveEvents) { - // LoadPlays({ - // scope: scope - // }); - // } - // }; - // scope.searchPlaysKeyPress = function(e) { - // if (e.keyCode === 13) { - // scope.searchPlays(); - // e.stopPropagation(); - // } - // }; - // scope.searchTasks = function() { - // var params = {}; - // if (scope.search_task_name) { - // scope.searchTasksEnabled = false; - // } - // else { - // scope.searchTasksEnabled = true; - // } - // if (!scope.liveEventProcessing || scope.pauseLiveEvents) { - // if (scope.search_task_status === 'failed'){ - // params.failed = true; - // } - // LoadTasks({ - // scope: scope - // }); - // } - // }; - // scope.searchTasksKeyPress = function(e) { - // if (e.keyCode === 13) { - // scope.searchTasks(); - // e.stopPropagation(); - // } - // }; - // scope.searchHosts = function() { - // var params = {}; - // if (scope.search_host_name) { - // scope.searchHostsEnabled = false; - // } - // else { - // scope.searchHostsEnabled = true; - // } - // if ((!scope.liveEventProcessing || scope.pauseLiveEvents) && scope.selectedTask) { - // scope.hostResultsLoading = true; - // params = { - // parent: scope.selectedTask, - // event__startswith: 'runner', - // page_size: scope.hostResultsMaxRows, - // order: 'host_name,counter', - // host_name__icontains: scope.search_host_name - // }; - // if (scope.search_host_status === 'failed'){ - // params.failed = true; - // } - // JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){ - // scope.hostResults = JobDetailService.processHostEvents(res.results); - // scope.hostResultsLoading = false; - // }); - // } - // }; - // scope.searchHostsKeyPress = function(e) { - // if (e.keyCode === 13) { - // scope.searchHosts(); - // e.stopPropagation(); - // } - // }; - if (scope.removeDeleteFinished) { scope.removeDeleteFinished(); } diff --git a/awx/ui/client/src/management-jobs/card/card.controller.js b/awx/ui/client/src/management-jobs/card/card.controller.js index 19e521804f..29fb0da07a 100644 --- a/awx/ui/client/src/management-jobs/card/card.controller.js +++ b/awx/ui/client/src/management-jobs/card/card.controller.js @@ -51,14 +51,6 @@ export default //ignore } - // @issue: OLD SEARCH - // if (scope.searchCleanup) { - // scope.searchCleanup(); - // } - // else { - // Wait('stop'); - // } - Wait('stop'); }; @@ -274,6 +266,7 @@ export default }; parent_scope.refreshJobs = function(){ + // TODO: what should I do here? // @issue: OLD SEARCH // scope.search(SchedulesList.iterator); }; diff --git a/awx/ui/client/src/organizations/edit/organizations-edit.controller.js b/awx/ui/client/src/organizations/edit/organizations-edit.controller.js index 6d06679ffa..b6ecfae2a4 100644 --- a/awx/ui/client/src/organizations/edit/organizations-edit.controller.js +++ b/awx/ui/client/src/organizations/edit/organizations-edit.controller.js @@ -121,9 +121,6 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', 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'); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index 7032ef5f81..f2ab1307f2 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -118,11 +118,7 @@ export default ['$scope', '$rootScope', '$location', '$log', // 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') { - // @issue: OLD SEARCH - // $scope.search(list.iterator, null, null, null, null, false); - - } else { + if (!(data.status === 'successful' || data.status === 'failed')) { project.scm_update_tooltip = "SCM update currently running"; project.scm_type_class = "btn-disabled"; } @@ -289,6 +285,7 @@ export default ['$scope', '$rootScope', '$location', '$log', }; $scope.refresh = function() { + // TODO: what should I do here? // @issue: OLD SEARCH // $scope.search(list.iterator); }; @@ -301,13 +298,7 @@ export default ['$scope', '$rootScope', '$location', '$log', } $scope.projects.forEach(function(project) { if (project.id === project_id) { - if (project.scm_type === "Manual" || Empty(project.scm_type)) { - // Do not respond. Button appears greyed out as if it is disabled. Not disabled though, because we need mouse over event - // to work. So user can click, but we just won't do anything. - //Alert('Missing SCM Setup', 'Before running an SCM update, edit the project and provide the SCM access information.', 'alert-info'); - } else if (project.status === 'updating' || project.status === 'running' || project.status === 'pending') { - // Alert('Update in Progress', 'The SCM update process is running. Use the Refresh button to monitor the status.', 'alert-info'); - } else { + if (!((project.scm_type === "Manual" || Empty(project.scm_type)) || (project.status === 'updating' || project.status === 'running' || project.status === 'pending'))) { ProjectUpdate({ scope: $scope, project_id: project.id }); } } diff --git a/awx/ui/client/src/projects/edit/projects-edit.controller.js b/awx/ui/client/src/projects/edit/projects-edit.controller.js index db4a647dd5..05b7cf48e7 100644 --- a/awx/ui/client/src/projects/edit/projects-edit.controller.js +++ b/awx/ui/client/src/projects/edit/projects-edit.controller.js @@ -227,8 +227,6 @@ export default ['$scope', '$rootScope', '$compile', '$location', '$log', 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'); From d6ba1342347c6b2a5b11295bd2bdeec35674e21f Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Tue, 28 Feb 2017 15:53:15 -0500 Subject: [PATCH 08/39] Helpers directory cleanup --- .../rbac-multiselect-list.directive.js | 4 +- .../get-target-title.factory.js | 51 + awx/ui/client/src/activity-stream/main.js | 6 +- .../model-to-base-path-key.factory.js | 59 + awx/ui/client/src/app.js | 27 +- .../add/credentials-add.controller.js | 6 +- .../edit/credentials-edit.controller.js | 6 +- .../factories/become-method-change.factory.js | 105 ++ .../factories/credential-form-save.factory.js | 113 ++ .../factories/kind-change.factory.js | 192 +++ .../factories/owner-change.factory.js | 18 + awx/ui/client/src/credentials/main.js | 8 + awx/ui/client/src/helpers.js | 48 +- awx/ui/client/src/helpers/ActivityStream.js | 64 - awx/ui/client/src/helpers/Adhoc.js | 172 --- awx/ui/client/src/helpers/ApiModel.js | 63 - awx/ui/client/src/helpers/Children.js | 111 -- awx/ui/client/src/helpers/Credentials.js | 441 ------- awx/ui/client/src/helpers/Events.js | 237 ---- awx/ui/client/src/helpers/Groups.js | 1047 ----------------- awx/ui/client/src/helpers/Hosts.js | 465 -------- awx/ui/client/src/helpers/JobSubmission.js | 337 ------ awx/ui/client/src/helpers/JobTemplates.js | 173 --- awx/ui/client/src/helpers/Jobs.js | 308 ----- awx/ui/client/src/helpers/Parse.js | 108 -- awx/ui/client/src/helpers/ProjectPath.js | 91 -- awx/ui/client/src/helpers/Projects.js | 84 -- awx/ui/client/src/helpers/Schedules.js | 506 -------- awx/ui/client/src/helpers/Selection.js | 177 --- awx/ui/client/src/helpers/Users.js | 46 - awx/ui/client/src/helpers/Variables.js | 180 --- awx/ui/client/src/helpers/api-defaults.js | 94 -- awx/ui/client/src/helpers/inventory.js | 84 -- awx/ui/client/src/helpers/md5.js | 46 - awx/ui/client/src/helpers/teams.js | 76 -- .../dashboard/lists/job-templates/main.js | 3 +- .../factories/get-hosts-status-msg.factory.js | 33 + .../get-source-type-options.factory.js | 37 + .../factories/get-sync-status-msg.factory.js | 77 ++ .../factories/groups-cancel-update.factory.js | 81 ++ .../factories/view-update-status.factory.js | 46 + .../src/inventories/manage/groups/main.js | 10 + .../factories/set-enabled-msg.factory.js | 13 + .../hosts/factories/set-status.factory.js | 102 ++ .../src/inventories/manage/hosts/main.js | 4 + .../adhoc-run.factory.js | 162 +++ .../check-passwords.factory.js | 43 + .../create-launch-dialog.factory.js | 75 ++ .../inventory-update.factory.js | 76 ++ .../project-update.factory.js | 78 ++ .../prompt-for-passwords.factory.js | 82 ++ awx/ui/client/src/job-submission/main.js | 12 + .../src/jobs/factories/delete-job.factory.js | 137 +++ .../factories/job-status-tool-tip.factory.js | 31 + .../factories/jobs-list-update.factory.js | 49 + .../jobs/factories/relaunch-adhoc.factory.js | 11 + .../factories/relaunch-inventory.factory.js | 30 + .../jobs/factories/relaunch-job.factory.js | 28 + .../factories/relaunch-playbook.factory.js | 12 + .../jobs/factories/relaunch-scm.factory.js | 11 + awx/ui/client/src/jobs/main.js | 18 +- .../linkout/addUsers/addUsers.controller.js | 4 +- .../organizations-projects.controller.js | 4 +- .../organizations-teams.controller.js | 8 +- .../factories/get-project-icon.factory.js | 30 + .../factories/get-project-path.factory.js | 78 ++ .../factories/get-project-tool-tip.factory.js | 38 + awx/ui/client/src/projects/main.js | 6 + .../factories/add-schedule.factory.js | 135 +++ .../factories/delete-schedule.factory.js | 61 + .../factories/edit-schedule.factory.js | 154 +++ .../factories/r-rule-to-api.factory.js | 10 + .../factories/schedule-post.factory.js | 78 ++ .../factories/toggle-schedule.factory.js | 46 + awx/ui/client/src/scheduler/main.js | 12 + awx/ui/client/src/shared/Modal.js | 2 +- awx/ui/client/src/shared/directives.js | 2 +- .../load-config/load-config.factory.js} | 33 +- awx/ui/client/src/shared/load-config/main.js | 5 + awx/ui/client/src/shared/parse/main.js | 5 + .../shared/parse/parse-type-change.factory.js | 93 ++ awx/ui/client/src/shared/variables/main.js | 9 + .../parse-variable-string.factory.js | 55 + .../variables/sort-variables.factory.js | 23 + .../src/shared/variables/to-json.factory.js | 80 ++ .../factories/callback-help-init.factory.js | 156 +++ .../factories/md-5-setup.factory.js | 30 + awx/ui/client/src/templates/main.js | 4 + .../src/users/add/users-add.controller.js | 4 +- .../src/users/edit/users-edit.controller.js | 4 +- 90 files changed, 2952 insertions(+), 5031 deletions(-) create mode 100644 awx/ui/client/src/activity-stream/get-target-title.factory.js create mode 100644 awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js create mode 100644 awx/ui/client/src/credentials/factories/become-method-change.factory.js create mode 100644 awx/ui/client/src/credentials/factories/credential-form-save.factory.js create mode 100644 awx/ui/client/src/credentials/factories/kind-change.factory.js create mode 100644 awx/ui/client/src/credentials/factories/owner-change.factory.js delete mode 100644 awx/ui/client/src/helpers/ActivityStream.js delete mode 100644 awx/ui/client/src/helpers/Adhoc.js delete mode 100644 awx/ui/client/src/helpers/ApiModel.js delete mode 100644 awx/ui/client/src/helpers/Children.js delete mode 100644 awx/ui/client/src/helpers/Credentials.js delete mode 100644 awx/ui/client/src/helpers/Events.js delete mode 100644 awx/ui/client/src/helpers/Groups.js delete mode 100644 awx/ui/client/src/helpers/Hosts.js delete mode 100644 awx/ui/client/src/helpers/JobSubmission.js delete mode 100644 awx/ui/client/src/helpers/JobTemplates.js delete mode 100644 awx/ui/client/src/helpers/Jobs.js delete mode 100644 awx/ui/client/src/helpers/Parse.js delete mode 100644 awx/ui/client/src/helpers/ProjectPath.js delete mode 100644 awx/ui/client/src/helpers/Projects.js delete mode 100644 awx/ui/client/src/helpers/Schedules.js delete mode 100644 awx/ui/client/src/helpers/Selection.js delete mode 100644 awx/ui/client/src/helpers/Users.js delete mode 100644 awx/ui/client/src/helpers/Variables.js delete mode 100644 awx/ui/client/src/helpers/api-defaults.js delete mode 100644 awx/ui/client/src/helpers/inventory.js delete mode 100644 awx/ui/client/src/helpers/md5.js delete mode 100644 awx/ui/client/src/helpers/teams.js create mode 100644 awx/ui/client/src/inventories/manage/groups/factories/get-hosts-status-msg.factory.js create mode 100644 awx/ui/client/src/inventories/manage/groups/factories/get-source-type-options.factory.js create mode 100644 awx/ui/client/src/inventories/manage/groups/factories/get-sync-status-msg.factory.js create mode 100644 awx/ui/client/src/inventories/manage/groups/factories/groups-cancel-update.factory.js create mode 100644 awx/ui/client/src/inventories/manage/groups/factories/view-update-status.factory.js create mode 100644 awx/ui/client/src/inventories/manage/hosts/factories/set-enabled-msg.factory.js create mode 100644 awx/ui/client/src/inventories/manage/hosts/factories/set-status.factory.js create mode 100644 awx/ui/client/src/job-submission/job-submission-factories/adhoc-run.factory.js create mode 100644 awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js create mode 100644 awx/ui/client/src/job-submission/job-submission-factories/create-launch-dialog.factory.js create mode 100644 awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js create mode 100644 awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js create mode 100644 awx/ui/client/src/job-submission/job-submission-factories/prompt-for-passwords.factory.js create mode 100644 awx/ui/client/src/jobs/factories/delete-job.factory.js create mode 100644 awx/ui/client/src/jobs/factories/job-status-tool-tip.factory.js create mode 100644 awx/ui/client/src/jobs/factories/jobs-list-update.factory.js create mode 100644 awx/ui/client/src/jobs/factories/relaunch-adhoc.factory.js create mode 100644 awx/ui/client/src/jobs/factories/relaunch-inventory.factory.js create mode 100644 awx/ui/client/src/jobs/factories/relaunch-job.factory.js create mode 100644 awx/ui/client/src/jobs/factories/relaunch-playbook.factory.js create mode 100644 awx/ui/client/src/jobs/factories/relaunch-scm.factory.js create mode 100644 awx/ui/client/src/projects/factories/get-project-icon.factory.js create mode 100644 awx/ui/client/src/projects/factories/get-project-path.factory.js create mode 100644 awx/ui/client/src/projects/factories/get-project-tool-tip.factory.js create mode 100644 awx/ui/client/src/scheduler/factories/add-schedule.factory.js create mode 100644 awx/ui/client/src/scheduler/factories/delete-schedule.factory.js create mode 100644 awx/ui/client/src/scheduler/factories/edit-schedule.factory.js create mode 100644 awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js create mode 100644 awx/ui/client/src/scheduler/factories/schedule-post.factory.js create mode 100644 awx/ui/client/src/scheduler/factories/toggle-schedule.factory.js rename awx/ui/client/src/{helpers/LoadConfig.js => shared/load-config/load-config.factory.js} (82%) create mode 100644 awx/ui/client/src/shared/load-config/main.js create mode 100644 awx/ui/client/src/shared/parse/main.js create mode 100644 awx/ui/client/src/shared/parse/parse-type-change.factory.js create mode 100644 awx/ui/client/src/shared/variables/main.js create mode 100644 awx/ui/client/src/shared/variables/parse-variable-string.factory.js create mode 100644 awx/ui/client/src/shared/variables/sort-variables.factory.js create mode 100644 awx/ui/client/src/shared/variables/to-json.factory.js create mode 100644 awx/ui/client/src/templates/job_templates/factories/callback-help-init.factory.js create mode 100644 awx/ui/client/src/templates/job_templates/factories/md-5-setup.factory.js diff --git a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js index 8f3d924ec2..5b6825676f 100644 --- a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js +++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js @@ -6,9 +6,9 @@ /* jshint unused: vars */ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateList', 'ProjectList', - 'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath', 'SelectionInit', + 'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath', function(addPermissionsTeamsList, addPermissionsUsersList, TemplateList, ProjectList, - InventoryList, CredentialList, $compile, generateList, GetBasePath, SelectionInit) { + InventoryList, CredentialList, $compile, generateList, GetBasePath) { return { restrict: 'E', scope: { diff --git a/awx/ui/client/src/activity-stream/get-target-title.factory.js b/awx/ui/client/src/activity-stream/get-target-title.factory.js new file mode 100644 index 0000000000..85c6c7a80b --- /dev/null +++ b/awx/ui/client/src/activity-stream/get-target-title.factory.js @@ -0,0 +1,51 @@ +export default + function GetTargetTitle(i18n) { + return function (target) { + + var rtnTitle = i18n._('ALL ACTIVITY'); + + switch(target) { + case 'project': + rtnTitle = i18n._('PROJECTS'); + break; + case 'inventory': + rtnTitle = i18n._('INVENTORIES'); + break; + case 'credential': + rtnTitle = i18n._('CREDENTIALS'); + break; + case 'user': + rtnTitle = i18n._('USERS'); + break; + case 'team': + rtnTitle = i18n._('TEAMS'); + break; + case 'notification_template': + rtnTitle = i18n._('NOTIFICATION TEMPLATES'); + break; + case 'organization': + rtnTitle = i18n._('ORGANIZATIONS'); + break; + case 'job': + rtnTitle = i18n._('JOBS'); + break; + case 'custom_inventory_script': + rtnTitle = i18n._('INVENTORY SCRIPTS'); + break; + case 'schedule': + rtnTitle = i18n._('SCHEDULES'); + break; + case 'host': + rtnTitle = i18n._('HOSTS'); + break; + case 'template': + rtnTitle = i18n._('TEMPLATES'); + break; + } + + return rtnTitle; + + }; + } + +GetTargetTitle.$inject = ['i18n']; diff --git a/awx/ui/client/src/activity-stream/main.js b/awx/ui/client/src/activity-stream/main.js index 381c421c87..0185aa7d42 100644 --- a/awx/ui/client/src/activity-stream/main.js +++ b/awx/ui/client/src/activity-stream/main.js @@ -6,14 +6,16 @@ import activityStreamRoute from './activitystream.route'; import activityStreamController from './activitystream.controller'; - import streamDropdownNav from './streamDropdownNav/stream-dropdown-nav.directive'; - import streamDetailModal from './streamDetailModal/main'; +import GetTargetTitle from './get-target-title.factory'; +import ModelToBasePathKey from './model-to-base-path-key.factory'; export default angular.module('activityStream', [streamDetailModal.name]) .controller('activityStreamController', activityStreamController) .directive('streamDropdownNav', streamDropdownNav) + .factory('GetTargetTitle', GetTargetTitle) + .factory('ModelToBasePathKey', ModelToBasePathKey) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState(activityStreamRoute); }]); diff --git a/awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js b/awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js new file mode 100644 index 0000000000..bc6cfe74b8 --- /dev/null +++ b/awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js @@ -0,0 +1,59 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + /** + * @ngdoc function + * @name helpers.function:ApiModel + * @description Helper functions to convert singular/plural versions of our models to the opposite +*/ + +export default + function ModelToBasePathKey() { + return function(model) { + // This function takes in the singular model string and returns the key needed + // to get the base path from $rootScope/local storage. + + var basePathKey; + + switch(model) { + case 'project': + basePathKey = 'projects'; + break; + case 'inventory': + basePathKey = 'inventory'; + break; + case 'job_template': + basePathKey = 'job_templates'; + break; + case 'credential': + basePathKey = 'credentials'; + break; + case 'user': + basePathKey = 'users'; + break; + case 'team': + basePathKey = 'teams'; + break; + case 'notification_template': + basePathKey = 'notification_templates'; + break; + case 'organization': + basePathKey = 'organizations'; + break; + case 'management_job': + basePathKey = 'management_jobs'; + break; + case 'custom_inventory_script': + basePathKey = 'inventory_scripts'; + break; + case 'workflow_job_template': + basePathKey = 'workflow_job_templates'; + break; + } + + return basePathKey; + }; + } diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index aade185211..7123d16dc6 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -70,6 +70,9 @@ import jobs from './jobs/main'; import teams from './teams/main'; import users from './users/main'; import projects from './projects/main'; +import variables from './shared/variables/main'; +import parse from './shared/parse/main'; +import loadconfig from './shared/load-config/main'; import RestServices from './rest/main'; import access from './access/main'; @@ -93,6 +96,7 @@ var tower = angular.module('Tower', [ require('angular-sanitize'), require('angular-scheduler').name, require('angular-tz-extensions'), + require('angular-md5'), require('lr-infinite-scroll'), require('ng-toast'), 'gettext', @@ -134,6 +138,9 @@ var tower = angular.module('Tower', [ teams.name, users.name, projects.name, + variables.name, + parse.name, + loadconfig.name, //'templates', 'Utilities', 'OrganizationFormDefinition', @@ -141,68 +148,48 @@ var tower = angular.module('Tower', [ 'OrganizationListDefinition', 'templates', 'UserListDefinition', - 'UserHelper', 'PromptDialog', 'AWDirectives', 'InventoriesListDefinition', 'InventoryFormDefinition', - 'InventoryHelper', 'InventoryGroupsDefinition', 'InventoryHostsDefinition', - 'HostsHelper', 'AWFilters', 'HostFormDefinition', 'HostListDefinition', 'GroupFormDefinition', 'GroupListDefinition', - 'GroupsHelper', 'TeamsListDefinition', 'TeamFormDefinition', - 'TeamHelper', 'CredentialsListDefinition', 'CredentialFormDefinition', 'TemplatesListDefinition', 'PortalJobTemplatesListDefinition', 'JobTemplateFormDefinition', - 'JobTemplatesHelper', - 'JobSubmissionHelper', 'ProjectsListDefinition', 'ProjectFormDefinition', 'ProjectStatusDefinition', - 'ProjectsHelper', 'CompletedJobsDefinition', 'AllJobsDefinition', 'JobSummaryDefinition', - 'ParseHelper', - 'ChildrenHelper', - 'ProjectPathHelper', - 'md5Helper', - 'SelectionHelper', 'HostGroupsFormDefinition', 'StreamWidget', - 'JobsHelper', - 'CredentialsHelper', 'StreamListDefinition', 'ActivityDetailDefinition', - 'VariablesHelper', 'SchedulesListDefinition', 'ScheduledJobsDefinition', //'Timezones', - 'SchedulesHelper', 'JobsListDefinition', 'LogViewerStatusDefinition', 'StandardOutHelper', 'LogViewerOptionsDefinition', 'JobDetailHelper', 'lrInfiniteScroll', - 'LoadConfigHelper', 'PortalJobsListDefinition', 'features', 'longDateFilter', 'pendolytics', scheduler.name, - 'ApiModelHelper', - 'ActivityStreamHelper', 'WorkflowFormDefinition', 'InventorySourcesListDefinition', 'WorkflowMakerFormDefinition' diff --git a/awx/ui/client/src/credentials/add/credentials-add.controller.js b/awx/ui/client/src/credentials/add/credentials-add.controller.js index fa6c436e66..88f2a41e57 100644 --- a/awx/ui/client/src/credentials/add/credentials-add.controller.js +++ b/awx/ui/client/src/credentials/add/credentials-add.controller.js @@ -7,11 +7,11 @@ export default ['$scope', '$rootScope', '$compile', '$location', '$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange', - 'OwnerChange', 'FormSave', '$state', 'CreateSelect2', 'i18n', + 'OwnerChange', 'CredentialFormSave', '$state', 'CreateSelect2', 'i18n', function($scope, $rootScope, $compile, $location, $log, $stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors, ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange, - OwnerChange, FormSave, $state, CreateSelect2, i18n) { + OwnerChange, CredentialFormSave, $state, CreateSelect2, i18n) { ClearScope(); @@ -126,7 +126,7 @@ export default ['$scope', '$rootScope', '$compile', '$location', // Save $scope.formSave = function() { if ($scope[form.name + '_form'].$valid) { - FormSave({ scope: $scope, mode: 'add' }); + CredentialFormSave({ scope: $scope, mode: 'add' }); } }; diff --git a/awx/ui/client/src/credentials/edit/credentials-edit.controller.js b/awx/ui/client/src/credentials/edit/credentials-edit.controller.js index 1926e811ca..d0fbf66258 100644 --- a/awx/ui/client/src/credentials/edit/credentials-edit.controller.js +++ b/awx/ui/client/src/credentials/edit/credentials-edit.controller.js @@ -8,10 +8,10 @@ export default ['$scope', '$rootScope', '$compile', '$location', '$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices', 'KindChange', 'BecomeMethodChange', 'Empty', 'OwnerChange', - 'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n', + 'CredentialFormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n', function($scope, $rootScope, $compile, $location, $log, $stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt, - GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, FormSave, Wait, + GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, CredentialFormSave, Wait, $state, CreateSelect2, Authorization, i18n) { ClearScope(); @@ -236,7 +236,7 @@ export default ['$scope', '$rootScope', '$compile', '$location', // Save changes to the parent $scope.formSave = function() { if ($scope[form.name + '_form'].$valid) { - FormSave({ scope: $scope, mode: 'edit' }); + CredentialFormSave({ scope: $scope, mode: 'edit' }); } }; diff --git a/awx/ui/client/src/credentials/factories/become-method-change.factory.js b/awx/ui/client/src/credentials/factories/become-method-change.factory.js new file mode 100644 index 0000000000..0727553528 --- /dev/null +++ b/awx/ui/client/src/credentials/factories/become-method-change.factory.js @@ -0,0 +1,105 @@ +export default + function BecomeMethodChange(Empty, i18n) { + return function(params) { + var scope = params.scope; + + if (!Empty(scope.kind)) { + // Apply kind specific settings + switch (scope.kind.value) { + case 'aws': + scope.aws_required = true; + break; + case 'rax': + scope.rackspace_required = true; + scope.username_required = true; + break; + case 'ssh': + scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' + scope.becomeUsernameLabel = i18n._('Privilege Escalation Username'); + scope.becomePasswordLabel = i18n._('Privilege Escalation Password'); + break; + case 'scm': + scope.sshKeyDataLabel = i18n._('SCM Private Key'); + scope.passwordLabel = i18n._('Password'); + break; + case 'gce': + scope.usernameLabel = i18n._('Service Account Email Address'); + scope.sshKeyDataLabel = i18n._('RSA Private Key'); + scope.email_required = true; + scope.key_required = true; + scope.project_required = true; + scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.'); + scope.projectLabel = i18n._("Project"); + scope.project_required = false; + scope.projectPopOver = "

" + i18n._("The Project ID is the " + + "GCE assigned identification. It is constructed as " + + "two words followed by a three digit number. Such " + + "as: ") + "

adjective-noun-000

"; + break; + case 'azure': + scope.sshKeyDataLabel = i18n._('Management Certificate'); + scope.subscription_required = true; + scope.key_required = true; + scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console."); + break; + case 'azure_rm': + scope.usernameLabel = i18n._("Username"); + scope.subscription_required = true; + scope.passwordLabel = i18n._('Password'); + scope.azure_rm_required = true; + break; + case 'vmware': + scope.username_required = true; + scope.host_required = true; + scope.password_required = true; + scope.hostLabel = "vCenter Host"; + scope.passwordLabel = i18n._('Password'); + scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter."); + break; + case 'openstack': + scope.hostLabel = i18n._("Host (Authentication URL)"); + scope.projectLabel = i18n._("Project (Tenant Name)"); + scope.domainLabel = i18n._("Domain Name"); + scope.password_required = true; + scope.project_required = true; + scope.host_required = true; + scope.username_required = true; + scope.projectPopOver = "

" + i18n._("This is the tenant name. " + + " This value is usually the same " + + " as the username.") + "

"; + scope.hostPopOver = "

" + i18n._("The host to authenticate with.") + + "
" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/"); + break; + case 'satellite6': + scope.username_required = true; + scope.password_required = true; + scope.passwordLabel = i18n._('Password'); + scope.host_required = true; + scope.hostLabel = i18n._("Satellite 6 URL"); + scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" + + "Red Hat Satellite 6 server. %s" + + "For example, %s"), "
", "
", "https://satellite.example.org"); + break; + case 'cloudforms': + scope.username_required = true; + scope.password_required = true; + scope.passwordLabel = i18n._('Password'); + scope.host_required = true; + scope.hostLabel = i18n._("CloudForms URL"); + scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" + + "corresponds to your CloudForm instance. %s" + + "For example, %s"), "
", "
", "https://cloudforms.example.org"); + break; + case 'net': + scope.username_required = true; + scope.password_required = false; + scope.passwordLabel = i18n._('Password'); + scope.sshKeyDataLabel = i18n._('SSH Key'); + break; + } + } + }; + } + +BecomeMethodChange.$inject = + [ 'Empty', 'i18n' ]; diff --git a/awx/ui/client/src/credentials/factories/credential-form-save.factory.js b/awx/ui/client/src/credentials/factories/credential-form-save.factory.js new file mode 100644 index 0000000000..f7c3c1e45f --- /dev/null +++ b/awx/ui/client/src/credentials/factories/credential-form-save.factory.js @@ -0,0 +1,113 @@ +export default + function CredentialFormSave($rootScope, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state, i18n) { + return function(params) { + var scope = params.scope, + mode = params.mode, + form = CredentialForm, + data = {}, fld, url; + + for (fld in form.fields) { + if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' && + fld !== 'ssh_password') { + if (fld === "organization" && !scope[fld]) { + data.user = $rootScope.current_user.id; + } else if (scope[fld] === null) { + data[fld] = ""; + } else { + data[fld] = scope[fld]; + } + } + } + + data.kind = scope.kind.value; + if (scope.become_method === null || typeof scope.become_method === 'undefined') { + data.become_method = ""; + data.become_username = ""; + data.become_password = ""; + } else { + data.become_method = (scope.become_method.value) ? scope.become_method.value : ""; + } + switch (data.kind) { + case 'ssh': + data.password = scope.ssh_password; + break; + case 'aws': + data.username = scope.access_key; + data.password = scope.secret_key; + break; + case 'rax': + data.password = scope.api_key; + break; + case 'gce': + data.username = scope.email_address; + data.project = scope.project; + break; + case 'azure': + data.username = scope.subscription; + } + + Wait('start'); + if (mode === 'add') { + url = GetBasePath("credentials"); + Rest.setUrl(url); + Rest.post(data) + .success(function (data) { + scope.addedItem = data.id; + + // @issue: OLD SEARCH + // Refresh({ + // scope: scope, + // set: 'credentials', + // iterator: 'credential', + // url: url + // }); + + Wait('stop'); + var base = $location.path().replace(/^\//, '').split('/')[0]; + if (base === 'credentials') { + $state.go('credentials.edit', {credential_id: data.id}, {reload: true}); + } + else { + ReturnToCaller(1); + } + }) + .error(function (data, status) { + Wait('stop'); + // TODO: hopefully this conditional error handling will to away in a future version of tower. The reason why we cannot + // simply pass this error to ProcessErrors is because it will actually match the form element 'ssh_key_unlock' and show + // the error there. The ssh_key_unlock field is not shown when the kind of credential is gce/azure and as a result the + // error is never shown. In the future, the API will hopefully either behave or respond differently. + if(status && status === 400 && data && data.ssh_key_unlock && (scope.kind.value === 'gce' || scope.kind.value === 'azure')) { + scope.ssh_key_data_api_error = i18n._("Encrypted credentials are not supported."); + } + else { + ProcessErrors(scope, data, status, form, { + hdr: i18n._('Error!'), + msg: i18n._('Failed to create new Credential. POST status: ') + status + }); + } + }); + } else { + url = GetBasePath('credentials') + scope.id + '/'; + Rest.setUrl(url); + Rest.put(data) + .success(function () { + Wait('stop'); + $state.go($state.current, {}, {reload: true}); + }) + .error(function (data, status) { + Wait('stop'); + ProcessErrors(scope, data, status, form, { + hdr: i18n._('Error!'), + msg: i18n._('Failed to update Credential. PUT status: ') + status + }); + }); + } + }; + } + +CredentialFormSave.$inject = + [ '$rootScope', '$location', 'Alert', 'Rest', + 'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm', + 'ReturnToCaller', 'Wait', '$state', 'i18n' + ]; diff --git a/awx/ui/client/src/credentials/factories/kind-change.factory.js b/awx/ui/client/src/credentials/factories/kind-change.factory.js new file mode 100644 index 0000000000..e35bedc526 --- /dev/null +++ b/awx/ui/client/src/credentials/factories/kind-change.factory.js @@ -0,0 +1,192 @@ +export default + function KindChange(Empty, i18n) { + return function(params) { + var scope = params.scope, + reset = params.reset, + collapse, id; + + $('.popover').each(function() { + // remove lingering popover

. Seems to be a bug in TB3 RC1 + $(this).remove(); + }); + $('.tooltip').each( function() { + // close any lingering tool tipss + $(this).hide(); + }); + // Put things in a default state + scope.usernameLabel = i18n._('Username'); + scope.aws_required = false; + scope.email_required = false; + scope.rackspace_required = false; + scope.sshKeyDataLabel = i18n._('Private Key'); + scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE) + scope.key_required = false; // JT -- doing the same for key and project + scope.project_required = false; + scope.subscription_required = false; + scope.key_description = i18n.sprintf(i18n._("Paste the contents of the SSH private key file.%s or click to close%s"), "
Esc", "
"); + scope.host_required = false; + scope.password_required = false; + scope.hostLabel = ''; + scope.passwordLabel = i18n._('Password'); + + $('.popover').each(function() { + // remove lingering popover
. Seems to be a bug in TB3 RC1 + $(this).remove(); + }); + $('.tooltip').each( function() { + // close any lingering tool tipss + $(this).hide(); + }); + // Put things in a default state + scope.usernameLabel = i18n._('Username'); + scope.aws_required = false; + scope.email_required = false; + scope.rackspace_required = false; + scope.sshKeyDataLabel = i18n._('Private Key'); + scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE) + scope.key_required = false; // JT -- doing the same for key and project + scope.project_required = false; + scope.domain_required = false; + scope.subscription_required = false; + scope.key_description = i18n._("Paste the contents of the SSH private key file."); + scope.host_required = false; + scope.password_required = false; + scope.hostLabel = ''; + scope.projectLabel = ''; + scope.domainLabel = ''; + scope.project_required = false; + scope.passwordLabel = i18n._('Password (API Key)'); + scope.projectPopOver = "

" + i18n._("The project value") + "

"; + scope.hostPopOver = "

" + i18n._("The host value") + "

"; + scope.ssh_key_data_api_error = ''; + + if (!Empty(scope.kind)) { + // Apply kind specific settings + switch (scope.kind.value) { + case 'aws': + scope.aws_required = true; + break; + case 'rax': + scope.rackspace_required = true; + scope.username_required = true; + break; + case 'ssh': + scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' + scope.becomeUsernameLabel = i18n._('Privilege Escalation Username'); + scope.becomePasswordLabel = i18n._('Privilege Escalation Password'); + break; + case 'scm': + scope.sshKeyDataLabel = i18n._('SCM Private Key'); + scope.passwordLabel = i18n._('Password'); + break; + case 'gce': + scope.usernameLabel = i18n._('Service Account Email Address'); + scope.sshKeyDataLabel = i18n._('RSA Private Key'); + scope.email_required = true; + scope.key_required = true; + scope.project_required = true; + scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.'); + scope.projectLabel = i18n._("Project"); + scope.project_required = false; + scope.projectPopOver = "

" + i18n._("The Project ID is the " + + "GCE assigned identification. It is constructed as " + + "two words followed by a three digit number. Such " + + "as: ") + "

adjective-noun-000

"; + break; + case 'azure': + scope.sshKeyDataLabel = i18n._('Management Certificate'); + scope.subscription_required = true; + scope.key_required = true; + scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console."); + break; + case 'azure_rm': + scope.usernameLabel = i18n._("Username"); + scope.subscription_required = true; + scope.passwordLabel = i18n._('Password'); + scope.azure_rm_required = true; + break; + case 'vmware': + scope.username_required = true; + scope.host_required = true; + scope.password_required = true; + scope.hostLabel = "vCenter Host"; + scope.passwordLabel = i18n._('Password'); + scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter."); + break; + case 'openstack': + scope.hostLabel = i18n._("Host (Authentication URL)"); + scope.projectLabel = i18n._("Project (Tenant Name)"); + scope.domainLabel = i18n._("Domain Name"); + scope.password_required = true; + scope.project_required = true; + scope.host_required = true; + scope.username_required = true; + scope.projectPopOver = "

" + i18n._("This is the tenant name. " + + " This value is usually the same " + + " as the username.") + "

"; + scope.hostPopOver = "

" + i18n._("The host to authenticate with.") + + "
" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/"); + break; + case 'satellite6': + scope.username_required = true; + scope.password_required = true; + scope.passwordLabel = i18n._('Password'); + scope.host_required = true; + scope.hostLabel = i18n._("Satellite 6 URL"); + scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" + + "Red Hat Satellite 6 server. %s" + + "For example, %s"), "
", "
", "https://satellite.example.org"); + break; + case 'cloudforms': + scope.username_required = true; + scope.password_required = true; + scope.passwordLabel = i18n._('Password'); + scope.host_required = true; + scope.hostLabel = i18n._("CloudForms URL"); + scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" + + "corresponds to your CloudForm instance. %s" + + "For example, %s"), "
", "
", "https://cloudforms.example.org"); + break; + case 'net': + scope.username_required = true; + scope.password_required = false; + scope.passwordLabel = i18n._('Password'); + scope.sshKeyDataLabel = i18n._('SSH Key'); + break; + } + } + + // Reset all the field values related to Kind. + if (reset) { + scope.access_key = null; + scope.secret_key = null; + scope.api_key = null; + scope.username = null; + scope.password = null; + scope.password_confirm = null; + scope.ssh_key_data = null; + scope.ssh_key_unlock = null; + scope.ssh_key_unlock_confirm = null; + scope.become_username = null; + scope.become_password = null; + scope.authorize = false; + scope.authorize_password = null; + } + + // Collapse or open help widget based on whether scm value is selected + collapse = $('#credential_kind').parent().find('.panel-collapse').first(); + id = collapse.attr('id'); + if (!Empty(scope.kind) && scope.kind.value !== '') { + if ($('#' + id + '-icon').hasClass('icon-minus')) { + scope.accordionToggle('#' + id); + } + } else { + if ($('#' + id + '-icon').hasClass('icon-plus')) { + scope.accordionToggle('#' + id); + } + } + }; + } + +KindChange.$inject = + [ 'Empty', 'i18n' ]; diff --git a/awx/ui/client/src/credentials/factories/owner-change.factory.js b/awx/ui/client/src/credentials/factories/owner-change.factory.js new file mode 100644 index 0000000000..60b77110bb --- /dev/null +++ b/awx/ui/client/src/credentials/factories/owner-change.factory.js @@ -0,0 +1,18 @@ +export default + function OwnerChange() { + return function(params) { + var scope = params.scope, + owner = scope.owner; + if (owner === 'team') { + scope.team_required = true; + scope.user_required = false; + scope.user = null; + scope.user_username = null; + } else { + scope.team_required = false; + scope.user_required = true; + scope.team = null; + scope.team_name = null; + } + }; + } diff --git a/awx/ui/client/src/credentials/main.js b/awx/ui/client/src/credentials/main.js index 16cc13aad0..e4bb2f6f2d 100644 --- a/awx/ui/client/src/credentials/main.js +++ b/awx/ui/client/src/credentials/main.js @@ -8,11 +8,19 @@ import ownerList from './ownerList.directive'; import CredentialsList from './list/credentials-list.controller'; import CredentialsAdd from './add/credentials-add.controller'; import CredentialsEdit from './edit/credentials-edit.controller'; +import BecomeMethodChange from './factories/become-method-change.factory'; +import CredentialFormSave from './factories/credential-form-save.factory'; +import KindChange from './factories/kind-change.factory'; +import OwnerChange from './factories/owner-change.factory'; import { N_ } from '../i18n'; export default angular.module('credentials', []) .directive('ownerList', ownerList) + .factory('BecomeMethodChange', BecomeMethodChange) + .factory('CredentialFormSave', CredentialFormSave) + .factory('KindChange', KindChange) + .factory('OwnerChange', OwnerChange) .controller('CredentialsList', CredentialsList) .controller('CredentialsAdd', CredentialsAdd) .controller('CredentialsEdit', CredentialsEdit) diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js index b22938443d..9094caee7e 100644 --- a/awx/ui/client/src/helpers.js +++ b/awx/ui/client/src/helpers.js @@ -7,54 +7,8 @@ import './forms'; import './lists'; -import Children from "./helpers/Children"; -import Credentials from "./helpers/Credentials"; -import Events from "./helpers/Events"; -import Groups from "./helpers/Groups"; -import Hosts from "./helpers/Hosts"; import JobDetail from "./helpers/JobDetail"; -import JobSubmission from "./helpers/JobSubmission"; -import JobTemplates from "./helpers/JobTemplates"; -import Jobs from "./helpers/Jobs"; -import LoadConfig from "./helpers/LoadConfig"; -import Parse from "./helpers/Parse"; -import ProjectPath from "./helpers/ProjectPath"; -import Projects from "./helpers/Projects"; -import Schedules from "./helpers/Schedules"; -import Selection from "./helpers/Selection"; -import Users from "./helpers/Users"; -import Variables from "./helpers/Variables"; -import ApiDefaults from "./helpers/api-defaults"; -import inventory from "./helpers/inventory"; -import MD5 from "./helpers/md5"; -import Teams from "./helpers/teams"; -import AdhocHelper from "./helpers/Adhoc"; -import ApiModelHelper from "./helpers/ApiModel"; -import ActivityStreamHelper from "./helpers/ActivityStream"; export - { Children, - Credentials, - Events, - Groups, - Hosts, - JobDetail, - JobSubmission, - JobTemplates, - Jobs, - LoadConfig, - Parse, - ProjectPath, - Projects, - Schedules, - Selection, - Users, - Variables, - ApiDefaults, - inventory, - MD5, - Teams, - AdhocHelper, - ApiModelHelper, - ActivityStreamHelper + { JobDetail }; diff --git a/awx/ui/client/src/helpers/ActivityStream.js b/awx/ui/client/src/helpers/ActivityStream.js deleted file mode 100644 index 02bd972eee..0000000000 --- a/awx/ui/client/src/helpers/ActivityStream.js +++ /dev/null @@ -1,64 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:ActivityStream - * @description Helper functions for the activity stream -*/ - -export default - angular.module('ActivityStreamHelper', ['Utilities']) - .factory('GetTargetTitle', ['i18n', - function (i18n) { - return function (target) { - - var rtnTitle = i18n._('ALL ACTIVITY'); - - switch(target) { - case 'project': - rtnTitle = i18n._('PROJECTS'); - break; - case 'inventory': - rtnTitle = i18n._('INVENTORIES'); - break; - case 'credential': - rtnTitle = i18n._('CREDENTIALS'); - break; - case 'user': - rtnTitle = i18n._('USERS'); - break; - case 'team': - rtnTitle = i18n._('TEAMS'); - break; - case 'notification_template': - rtnTitle = i18n._('NOTIFICATION TEMPLATES'); - break; - case 'organization': - rtnTitle = i18n._('ORGANIZATIONS'); - break; - case 'job': - rtnTitle = i18n._('JOBS'); - break; - case 'custom_inventory_script': - rtnTitle = i18n._('INVENTORY SCRIPTS'); - break; - case 'schedule': - rtnTitle = i18n._('SCHEDULES'); - break; - case 'host': - rtnTitle = i18n._('HOSTS'); - break; - case 'template': - rtnTitle = i18n._('TEMPLATES'); - break; - } - - return rtnTitle; - - }; - } - ]); diff --git a/awx/ui/client/src/helpers/Adhoc.js b/awx/ui/client/src/helpers/Adhoc.js deleted file mode 100644 index d29e591ce5..0000000000 --- a/awx/ui/client/src/helpers/Adhoc.js +++ /dev/null @@ -1,172 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name helpers.function:Adhoc - * @description These routines are shared by adhoc command related controllers. - * The content here is very similar to the JobSubmission helper, and in fact, - * certain services are pulled from that helper. This leads to an important - * point: if you need to create functionality that is shared between the command - * and playbook run process, put that code in the JobSubmission helper and make - * it into a reusable step (by specifying a callback parameter in the factory). - * For a good example of this, please see how the AdhocLaunch factory in this - * file utilizes the CheckPasswords factory from the JobSubmission helper. - * - * #AdhocRelaunch Step 1: preparing the GET to ad_hoc_commands/n/relaunch - * The adhoc relaunch process is called from the JobSubmission helper. It is a - * separate process from the initial adhoc run becuase of the way the API - * endpoints work. For AdhocRelaunch, we have access to the original run and - * we can pull the related relaunch URL by knowing the original Adhoc runs ID. - * - * #AdhocRelaunch Step 2: If we got passwords back, add them - * The relaunch URL gives us back the passwords we need to prompt for (if any). - * We'll go to step 3 if there are passwords, and step 4 if not. - * - * #AdhocRelaunch Step 3: PromptForPasswords and the CreateLaunchDialog - * - * #AdhocRelaunch Step 5: StartAdhocRun - * - * #AdhocRelaunch Step 6: LaunchJob and navigate to the standard out page. - - * **If you are - * TODO: once the API endpoint is figured out for running an adhoc command - * from the form is figured out, the rest work should probably be excised from - * the controller and moved into here. See the todo statements in the - * controller for more information about this. - */ - -export default - angular.module('AdhocHelper', ['RestServices', 'Utilities', - 'CredentialFormDefinition', 'CredentialsListDefinition', - 'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', - 'FormGenerator', 'JobVarsPromptFormDefinition']) - - /** - * @ngdoc method - * @name helpers.function:JobSubmission#AdhocRun - * @methodOf helpers.function:JobSubmission - * @description The adhoc Run function is run when the user clicks the relaunch button - * - */ - // Submit request to run an adhoc comamand - .factory('AdhocRun', ['$location','$stateParams', 'LaunchJob', - 'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors', - 'Wait', 'Empty', 'CreateLaunchDialog', '$state', - function ($location, $stateParams, LaunchJob, PromptForPasswords, - Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog, $state) { - return function (params) { - var id = params.project_id, - scope = params.scope.$new(), - new_job_id, - html, - url; - - // this is used to cancel a running adhoc command from - // the jobs page - if (scope.removeCancelJob) { - scope.removeCancelJob(); - } - scope.removeCancelJob = scope.$on('CancelJob', function() { - // Delete the job - Wait('start'); - Rest.setUrl(GetBasePath('ad_hoc_commands') + new_job_id + '/'); - Rest.destroy() - .success(function() { - Wait('stop'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, - null, { hdr: 'Error!', - msg: 'Call to ' + url + - ' failed. DELETE returned status: ' + - status }); - }); - }); - - if (scope.removeStartAdhocRun) { - scope.removeStartAdhocRun(); - } - - scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() { - var password, - postData={}; - for (password in scope.passwords) { - postData[scope.passwords[password]] = scope[ - scope.passwords[password] - ]; - } - // Re-launch the adhoc job - Rest.setUrl(url); - Rest.post(postData) - .success(function (data) { - Wait('stop'); - if($location.path().replace(/^\//, '').split('/')[0] !== 'jobs') { - $state.go('adHocJobStdout', {id: data.id}); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, { - hdr: 'Error!', - msg: 'Failed to launch adhoc command. POST ' + - 'returned status: ' + status }); - }); - }); - - // start routine only if passwords need to be prompted - if (scope.removeCreateLaunchDialog) { - scope.removeCreateLaunchDialog(); - } - scope.removeCreateLaunchDialog = scope.$on('CreateLaunchDialog', - function(e, html, url) { - CreateLaunchDialog({ - scope: scope, - html: html, - url: url, - callback: 'StartAdhocRun' - }); - }); - - if (scope.removePromptForPasswords) { - scope.removePromptForPasswords(); - } - scope.removePromptForPasswords = scope.$on('PromptForPasswords', - function(e, passwords_needed_to_start,html, url) { - PromptForPasswords({ - scope: scope, - passwords: passwords_needed_to_start, - callback: 'CreateLaunchDialog', - html: html, - url: url - }); - }); // end password prompting routine - - // start the adhoc relaunch routine - Wait('start'); - url = GetBasePath('ad_hoc_commands') + id + '/relaunch/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - new_job_id = data.id; - - scope.passwords_needed_to_start = data.passwords_needed_to_start; - if (!Empty(data.passwords_needed_to_start) && - data.passwords_needed_to_start.length > 0) { - // go through the password prompt routine before - // starting the adhoc run - scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url); - } - else { - // no prompting of passwords needed - scope.$emit('StartAdhocRun'); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job template details. GET returned status: ' + status }); - }); - }; - }]); diff --git a/awx/ui/client/src/helpers/ApiModel.js b/awx/ui/client/src/helpers/ApiModel.js deleted file mode 100644 index 20bed707f2..0000000000 --- a/awx/ui/client/src/helpers/ApiModel.js +++ /dev/null @@ -1,63 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:ApiModel - * @description Helper functions to convert singular/plural versions of our models to the opposite -*/ - -export default - angular.module('ApiModelHelper', ['Utilities']) - .factory('ModelToBasePathKey', [ - function () { - return function (model) { - // This function takes in the singular model string and returns the key needed - // to get the base path from $rootScope/local storage. - - var basePathKey; - - switch(model) { - case 'project': - basePathKey = 'projects'; - break; - case 'inventory': - basePathKey = 'inventory'; - break; - case 'job_template': - basePathKey = 'job_templates'; - break; - case 'credential': - basePathKey = 'credentials'; - break; - case 'user': - basePathKey = 'users'; - break; - case 'team': - basePathKey = 'teams'; - break; - case 'notification_template': - basePathKey = 'notification_templates'; - break; - case 'organization': - basePathKey = 'organizations'; - break; - case 'management_job': - basePathKey = 'management_jobs'; - break; - case 'custom_inventory_script': - basePathKey = 'inventory_scripts'; - break; - case 'workflow_job_template': - basePathKey = 'workflow_job_templates'; - break; - } - - return basePathKey; - - }; - } - ]); diff --git a/awx/ui/client/src/helpers/Children.js b/awx/ui/client/src/helpers/Children.js deleted file mode 100644 index 7c8246dd62..0000000000 --- a/awx/ui/client/src/helpers/Children.js +++ /dev/null @@ -1,111 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Children - * @descriptionUsed in job_events to expand/collapse children by setting the - * 'show' attribute of each job_event in the set of job_events. - * See the filter in job_events.js list. -*/ - -export default - angular.module('ChildrenHelper', ['RestServices', 'Utilities']) - .factory('ToggleChildren', ['$location', 'Store', function ($location, Store) { - return function (params) { - - var scope = params.scope, - list = params.list, - id = params.id, - set = scope[list.name], - clicked, - //base = $location.path().replace(/^\//, '').split('/')[0], - path = $location.path(), - local_child_store; - - function updateExpand(key, expand) { - var found = false; - local_child_store.every(function(child, i) { - if (child.key === key) { - local_child_store[i].expand = expand; - found = true; - return false; - } - return true; - }); - if (!found) { - local_child_store.push({ key: key, expand: expand }); - } - } - - function updateShow(key, show) { - var found = false; - local_child_store.every(function(child, i) { - if (child.key === key) { - local_child_store[i].show = show; - found = true; - return false; - } - return true; - }); - if (!found) { - local_child_store.push({ key: key, show: show }); - } - } - - function expand(node) { - var i, has_children = false; - for (i = node + 1; i < set.length; i++) { - if (set[i].parent === set[node].id) { - updateShow(set[i].key, true); - set[i].show = true; - } - } - set[node].ngicon = (has_children) ? 'fa fa-minus-square-o node-toggle' : 'fa fa-minus-square-o node-toggle'; - } - - function collapse(node) { - var i, has_children = false; - for (i = node + 1; i < set.length; i++) { - if (set[i].parent === set[node].id) { - set[i].show = false; - has_children = true; - updateShow(set[i].key, false); - if (set[i].related.children) { - collapse(i); - } - } - } - set[node].ngicon = (has_children) ? 'fa fa-plus-square-o node-toggle' : 'fa fa-square-o node-toggle'; - } - - local_child_store = Store(path + '_children'); - if (!local_child_store) { - local_child_store = []; - } - - // Scan the array list and find the clicked element - set.every(function(row, i) { - if (row.id === id) { - clicked = i; - return false; - } - return true; - }); - - // Expand or collapse children based on clicked element's icon - if (/plus-square-o/.test(set[clicked].ngicon)) { - // Expand: lookup and display children - expand(clicked); - updateExpand(set[clicked].key, true); - } else if (/minus-square-o/.test(set[clicked].ngicon)) { - collapse(clicked); - updateExpand(set[clicked].key, false); - } - Store(path + '_children', local_child_store); - }; - } - ]); diff --git a/awx/ui/client/src/helpers/Credentials.js b/awx/ui/client/src/helpers/Credentials.js deleted file mode 100644 index 007756d17c..0000000000 --- a/awx/ui/client/src/helpers/Credentials.js +++ /dev/null @@ -1,441 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name helpers.function:Credentials - * @description Functions shared amongst Credential related controllers - */ - -export default -angular.module('CredentialsHelper', ['Utilities']) - -.factory('KindChange', ['Empty', 'i18n', - function (Empty, i18n) { - return function (params) { - var scope = params.scope, - reset = params.reset, - collapse, id; - - $('.popover').each(function() { - // remove lingering popover

. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each( function() { - // close any lingering tool tipss - $(this).hide(); - }); - // Put things in a default state - scope.usernameLabel = i18n._('Username'); - scope.aws_required = false; - scope.email_required = false; - scope.rackspace_required = false; - scope.sshKeyDataLabel = i18n._('Private Key'); - scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE) - scope.key_required = false; // JT -- doing the same for key and project - scope.project_required = false; - scope.subscription_required = false; - scope.key_description = i18n.sprintf(i18n._("Paste the contents of the SSH private key file.%s or click to close%s"), "
Esc", "
"); - scope.host_required = false; - scope.password_required = false; - scope.hostLabel = ''; - scope.passwordLabel = i18n._('Password'); - - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each( function() { - // close any lingering tool tipss - $(this).hide(); - }); - // Put things in a default state - scope.usernameLabel = i18n._('Username'); - scope.aws_required = false; - scope.email_required = false; - scope.rackspace_required = false; - scope.sshKeyDataLabel = i18n._('Private Key'); - scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE) - scope.key_required = false; // JT -- doing the same for key and project - scope.project_required = false; - scope.domain_required = false; - scope.subscription_required = false; - scope.key_description = i18n._("Paste the contents of the SSH private key file."); - scope.host_required = false; - scope.password_required = false; - scope.hostLabel = ''; - scope.projectLabel = ''; - scope.domainLabel = ''; - scope.project_required = false; - scope.passwordLabel = i18n._('Password (API Key)'); - scope.projectPopOver = "

" + i18n._("The project value") + "

"; - scope.hostPopOver = "

" + i18n._("The host value") + "

"; - scope.ssh_key_data_api_error = ''; - - if (!Empty(scope.kind)) { - // Apply kind specific settings - switch (scope.kind.value) { - case 'aws': - scope.aws_required = true; - break; - case 'rax': - scope.rackspace_required = true; - scope.username_required = true; - break; - case 'ssh': - scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' - scope.becomeUsernameLabel = i18n._('Privilege Escalation Username'); - scope.becomePasswordLabel = i18n._('Privilege Escalation Password'); - break; - case 'scm': - scope.sshKeyDataLabel = i18n._('SCM Private Key'); - scope.passwordLabel = i18n._('Password'); - break; - case 'gce': - scope.usernameLabel = i18n._('Service Account Email Address'); - scope.sshKeyDataLabel = i18n._('RSA Private Key'); - scope.email_required = true; - scope.key_required = true; - scope.project_required = true; - scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.'); - scope.projectLabel = i18n._("Project"); - scope.project_required = false; - scope.projectPopOver = "

" + i18n._("The Project ID is the " + - "GCE assigned identification. It is constructed as " + - "two words followed by a three digit number. Such " + - "as: ") + "

adjective-noun-000

"; - break; - case 'azure': - scope.sshKeyDataLabel = i18n._('Management Certificate'); - scope.subscription_required = true; - scope.key_required = true; - scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console."); - break; - case 'azure_rm': - scope.usernameLabel = i18n._("Username"); - scope.subscription_required = true; - scope.passwordLabel = i18n._('Password'); - scope.azure_rm_required = true; - break; - case 'vmware': - scope.username_required = true; - scope.host_required = true; - scope.password_required = true; - scope.hostLabel = "vCenter Host"; - scope.passwordLabel = i18n._('Password'); - scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter."); - break; - case 'openstack': - scope.hostLabel = i18n._("Host (Authentication URL)"); - scope.projectLabel = i18n._("Project (Tenant Name)"); - scope.domainLabel = i18n._("Domain Name"); - scope.password_required = true; - scope.project_required = true; - scope.host_required = true; - scope.username_required = true; - scope.projectPopOver = "

" + i18n._("This is the tenant name. " + - " This value is usually the same " + - " as the username.") + "

"; - scope.hostPopOver = "

" + i18n._("The host to authenticate with.") + - "
" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/"); - break; - case 'satellite6': - scope.username_required = true; - scope.password_required = true; - scope.passwordLabel = i18n._('Password'); - scope.host_required = true; - scope.hostLabel = i18n._("Satellite 6 URL"); - scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" + - "Red Hat Satellite 6 server. %s" + - "For example, %s"), "
", "
", "https://satellite.example.org"); - break; - case 'cloudforms': - scope.username_required = true; - scope.password_required = true; - scope.passwordLabel = i18n._('Password'); - scope.host_required = true; - scope.hostLabel = i18n._("CloudForms URL"); - scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" + - "corresponds to your CloudForm instance. %s" + - "For example, %s"), "
", "
", "https://cloudforms.example.org"); - break; - case 'net': - scope.username_required = true; - scope.password_required = false; - scope.passwordLabel = i18n._('Password'); - scope.sshKeyDataLabel = i18n._('SSH Key'); - break; - } - } - - // Reset all the field values related to Kind. - if (reset) { - scope.access_key = null; - scope.secret_key = null; - scope.api_key = null; - scope.username = null; - scope.password = null; - scope.password_confirm = null; - scope.ssh_key_data = null; - scope.ssh_key_unlock = null; - scope.ssh_key_unlock_confirm = null; - scope.become_username = null; - scope.become_password = null; - scope.authorize = false; - scope.authorize_password = null; - } - - // Collapse or open help widget based on whether scm value is selected - collapse = $('#credential_kind').parent().find('.panel-collapse').first(); - id = collapse.attr('id'); - if (!Empty(scope.kind) && scope.kind.value !== '') { - if ($('#' + id + '-icon').hasClass('icon-minus')) { - scope.accordionToggle('#' + id); - } - } else { - if ($('#' + id + '-icon').hasClass('icon-plus')) { - scope.accordionToggle('#' + id); - } - } - - }; - } -]) - -.factory('BecomeMethodChange', ['Empty', 'i18n', - function (Empty, i18n) { - return function (params) { - console.log('become method has changed'); - var scope = params.scope; - - if (!Empty(scope.kind)) { - // Apply kind specific settings - switch (scope.kind.value) { - case 'aws': - scope.aws_required = true; - break; - case 'rax': - scope.rackspace_required = true; - scope.username_required = true; - break; - case 'ssh': - scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' - scope.becomeUsernameLabel = i18n._('Privilege Escalation Username'); - scope.becomePasswordLabel = i18n._('Privilege Escalation Password'); - break; - case 'scm': - scope.sshKeyDataLabel = i18n._('SCM Private Key'); - scope.passwordLabel = i18n._('Password'); - break; - case 'gce': - scope.usernameLabel = i18n._('Service Account Email Address'); - scope.sshKeyDataLabel = i18n._('RSA Private Key'); - scope.email_required = true; - scope.key_required = true; - scope.project_required = true; - scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.'); - scope.projectLabel = i18n._("Project"); - scope.project_required = false; - scope.projectPopOver = "

" + i18n._("The Project ID is the " + - "GCE assigned identification. It is constructed as " + - "two words followed by a three digit number. Such " + - "as: ") + "

adjective-noun-000

"; - break; - case 'azure': - scope.sshKeyDataLabel = i18n._('Management Certificate'); - scope.subscription_required = true; - scope.key_required = true; - scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console."); - break; - case 'azure_rm': - scope.usernameLabel = i18n._("Username"); - scope.subscription_required = true; - scope.passwordLabel = i18n._('Password'); - scope.azure_rm_required = true; - break; - case 'vmware': - scope.username_required = true; - scope.host_required = true; - scope.password_required = true; - scope.hostLabel = "vCenter Host"; - scope.passwordLabel = i18n._('Password'); - scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter."); - break; - case 'openstack': - scope.hostLabel = i18n._("Host (Authentication URL)"); - scope.projectLabel = i18n._("Project (Tenant Name)"); - scope.domainLabel = i18n._("Domain Name"); - scope.password_required = true; - scope.project_required = true; - scope.host_required = true; - scope.username_required = true; - scope.projectPopOver = "

" + i18n._("This is the tenant name. " + - " This value is usually the same " + - " as the username.") + "

"; - scope.hostPopOver = "

" + i18n._("The host to authenticate with.") + - "
" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/"); - break; - case 'satellite6': - scope.username_required = true; - scope.password_required = true; - scope.passwordLabel = i18n._('Password'); - scope.host_required = true; - scope.hostLabel = i18n._("Satellite 6 URL"); - scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" + - "Red Hat Satellite 6 server. %s" + - "For example, %s"), "
", "
", "https://satellite.example.org"); - break; - case 'cloudforms': - scope.username_required = true; - scope.password_required = true; - scope.passwordLabel = i18n._('Password'); - scope.host_required = true; - scope.hostLabel = i18n._("CloudForms URL"); - scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" + - "corresponds to your CloudForm instance. %s" + - "For example, %s"), "
", "
", "https://cloudforms.example.org"); - break; - case 'net': - scope.username_required = true; - scope.password_required = false; - scope.passwordLabel = i18n._('Password'); - scope.sshKeyDataLabel = i18n._('SSH Key'); - break; - } - } - }; - } -]) - - -.factory('OwnerChange', [ - function () { - return function (params) { - var scope = params.scope, - owner = scope.owner; - if (owner === 'team') { - scope.team_required = true; - scope.user_required = false; - scope.user = null; - scope.user_username = null; - } else { - scope.team_required = false; - scope.user_required = true; - scope.team = null; - scope.team_name = null; - } - }; -} -]) - -.factory('FormSave', ['$rootScope', '$location', 'Alert', 'Rest', 'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm', 'ReturnToCaller', 'Wait', '$state', 'i18n', - function ($rootScope, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state, i18n) { - return function (params) { - var scope = params.scope, - mode = params.mode, - form = CredentialForm, - data = {}, fld, url; - - for (fld in form.fields) { - if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' && - fld !== 'ssh_password') { - if (fld === "organization" && !scope[fld]) { - data.user = $rootScope.current_user.id; - } else if (scope[fld] === null) { - data[fld] = ""; - } else { - data[fld] = scope[fld]; - } - } - } - - data.kind = scope.kind.value; - if (scope.become_method === null || typeof scope.become_method === 'undefined') { - data.become_method = ""; - data.become_username = ""; - data.become_password = ""; - } else { - data.become_method = (scope.become_method.value) ? scope.become_method.value : ""; - } - switch (data.kind) { - case 'ssh': - data.password = scope.ssh_password; - break; - case 'aws': - data.username = scope.access_key; - data.password = scope.secret_key; - break; - case 'rax': - data.password = scope.api_key; - break; - case 'gce': - data.username = scope.email_address; - data.project = scope.project; - break; - case 'azure': - data.username = scope.subscription; - } - - Wait('start'); - if (mode === 'add') { - url = GetBasePath("credentials"); - Rest.setUrl(url); - Rest.post(data) - .success(function (data) { - scope.addedItem = data.id; - - // @issue: OLD SEARCH - // Refresh({ - // scope: scope, - // set: 'credentials', - // iterator: 'credential', - // url: url - // }); - - Wait('stop'); - var base = $location.path().replace(/^\//, '').split('/')[0]; - if (base === 'credentials') { - $state.go('credentials.edit', {credential_id: data.id}, {reload: true}); - } - else { - ReturnToCaller(1); - } - }) - .error(function (data, status) { - Wait('stop'); - // TODO: hopefully this conditional error handling will to away in a future version of tower. The reason why we cannot - // simply pass this error to ProcessErrors is because it will actually match the form element 'ssh_key_unlock' and show - // the error there. The ssh_key_unlock field is not shown when the kind of credential is gce/azure and as a result the - // error is never shown. In the future, the API will hopefully either behave or respond differently. - if(status && status === 400 && data && data.ssh_key_unlock && (scope.kind.value === 'gce' || scope.kind.value === 'azure')) { - scope.ssh_key_data_api_error = i18n._("Encrypted credentials are not supported."); - } - else { - ProcessErrors(scope, data, status, form, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to create new Credential. POST status: ') + status - }); - } - }); - } else { - url = GetBasePath('credentials') + scope.id + '/'; - Rest.setUrl(url); - Rest.put(data) - .success(function () { - Wait('stop'); - $state.go($state.current, {}, {reload: true}); - }) - .error(function (data, status) { - Wait('stop'); - ProcessErrors(scope, data, status, form, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to update Credential. PUT status: ') + status - }); - }); - } - }; - } -]); diff --git a/awx/ui/client/src/helpers/Events.js b/awx/ui/client/src/helpers/Events.js deleted file mode 100644 index be58e72661..0000000000 --- a/awx/ui/client/src/helpers/Events.js +++ /dev/null @@ -1,237 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Events - * @description EventView - show the job_events form in a modal dialog -*/ - -export default - angular.module('EventsHelper', ['RestServices', 'Utilities', 'JobEventDataDefinition', 'JobEventsFormDefinition']) - - .factory('EventView', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'JobEventDataForm', 'Empty', 'JobEventsForm', - function ($rootScope, $location, $log, $stateParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, - FormatDate, JobEventDataForm, Empty, JobEventsForm) { - return function (params) { - - var event_id = params.event_id, - generator = GenerateForm, - form = angular.copy(JobEventsForm), - scope, - defaultUrl = GetBasePath('base') + 'job_events/' + event_id + '/'; - - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl); - Rest.get() - .success(function (data) { - var i, n, fld, rows, txt, cDate; - - // If event_data is not available, remove fields that depend on it - if ($.isEmptyObject(data.event_data) || !data.event_data.res || typeof data.event_data.res === 'string') { - for (fld in form.fields) { - switch (fld) { - case 'start': - case 'end': - case 'delta': - case 'msg': - case 'stdout': - case 'stderr': - case 'msg': - case 'results': - case 'module_name': - case 'module_args': - case 'rc': - delete form.fields[fld]; - break; - } - } - } - - if ($.isEmptyObject(data.event_data) || !data.event_data.res || typeof data.event_data.res !== 'string') { - delete form.fields.traceback; - } - - // Remove remaining form fields that do not have a corresponding data value - for (fld in form.fields) { - switch (fld) { - case 'start': - case 'end': - case 'delta': - case 'msg': - case 'stdout': - case 'stderr': - case 'msg': - case 'rc': - if (data.event_data && data.event_data.res && Empty(data.event_data.res[fld])) { - delete form.fields[fld]; - } else { - if (form.fields[fld].type === 'textarea') { - n = data.event_data.res[fld].match(/\n/g); - rows = (n) ? n.length : 1; - rows = (rows > 10) ? 10 : rows; - rows = (rows < 3) ? 3 : rows; - form.fields[fld].rows = rows; - } - } - break; - case 'results': - if (data.event_data && data.event_data.res && data.event_data.res[fld] === undefined) { - // not defined - delete form.fields[fld]; - } else if (!Array.isArray(data.event_data.res[fld]) || data.event_data.res[fld].length === 0) { - // defined, but empty - delete form.fields[fld]; - } else { - // defined and not empty, so attempt to size the textarea field - txt = ''; - for (i = 0; i < data.event_data.res[fld].length; i++) { - txt += data.event_data.res[fld][i]; - } - if (txt === '') { - // there's an array, but the actual text is empty - delete form.fields[fld]; - } else { - n = txt.match(/\n/g); - rows = (n) ? n.length : 1; - rows = (rows > 10) ? 10 : rows; - rows = (rows < 3) ? 3 : rows; - form.fields[fld].rows = rows; - } - } - break; - case 'module_name': - case 'module_args': - if (data.event_data && data.event_data.res) { - if (data.event_data.res.invocation === undefined || - data.event_data.res.invocation[fld] === undefined) { - delete form.fields[fld]; - } - } - break; - } - } - - // load the form - scope = generator.inject(form, { - mode: 'edit', - modal: true, - related: false - }); - generator.reset(); - scope.formModalAction = function () { - $('#form-modal').modal("hide"); - }; - scope.formModalActionLabel = 'OK'; - scope.formModalCancelShow = false; - scope.formModalInfo = 'View JSON'; - $('#form-modal .btn-success').removeClass('btn-success').addClass('btn-none'); - $('#form-modal').addClass('skinny-modal'); - scope.formModalHeader = data.event_display.replace(/^\u00a0*/g, ''); - - // Respond to View JSON button - scope.formModalInfoAction = function () { - var generator = GenerateForm, - scope = generator.inject(JobEventDataForm, { - mode: 'edit', - modal: true, - related: false, - modal_selector: '#form-modal2', - modal_body_id: 'form-modal2-body', - modal_title_id: 'formModal2Header' - }); - generator.reset(); - scope.formModal2Header = data.event_display.replace(/^\u00a0*/g, ''); - scope.event_data = JSON.stringify(data.event_data, null, '\t'); - scope.formModal2ActionLabel = 'OK'; - scope.formModal2CancelShow = false; - scope.formModal2Info = false; - scope.formModalInfo = 'View JSON'; - scope.formModal2Action = function () { - $('#form-modal2').modal("hide"); - }; - $('#form-modal2 .btn-success').removeClass('btn-success').addClass('btn-none'); - }; - - if (typeof data.event_data.res === 'string') { - scope.traceback = data.event_data.res; - } - - for (fld in form.fields) { - switch (fld) { - case 'status': - if (data.failed) { - scope.status = 'error'; - } else if (data.changed) { - scope.status = 'changed'; - } else { - scope.status = 'success'; - } - break; - case 'created': - cDate = new Date(data.created); - scope.created = FormatDate(cDate); - break; - case 'host': - if (data.summary_fields && data.summary_fields.host) { - scope.host = data.summary_fields.host.name; - } - break; - case 'id': - case 'task': - case 'play': - scope[fld] = data[fld]; - break; - case 'start': - case 'end': - if (data.event_data && data.event_data.res && !Empty(data.event_data.res[fld])) { - scope[fld] = data.event_data.res[fld]; - } - - break; - case 'results': - if (Array.isArray(data.event_data.res[fld]) && data.event_data.res[fld].length > 0) { - txt = ''; - for (i = 0; i < data.event_data.res[fld].length; i++) { - txt += data.event_data.res[fld][i]; - } - if (txt !== '') { - scope[fld] = txt; - } - } - break; - case 'msg': - case 'stdout': - case 'stderr': - case 'delta': - case 'rc': - if (data.event_data && data.event_data.res && data.event_data.res[fld] !== undefined) { - scope[fld] = data.event_data.res[fld]; - } - break; - case 'module_name': - case 'module_args': - if (data.event_data.res && data.event_data.res.invocation) { - scope[fld] = data.event_data.res.invocation[fld]; - } - break; - } - } - - if (!scope.$$phase) { - scope.$digest(); - } - - }) - .error(function (data, status) { - $('#form-modal').modal("hide"); - ProcessErrors(scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to retrieve event: ' + event_id + '. GET status: ' + status }); - }); - }; - } - ]); diff --git a/awx/ui/client/src/helpers/Groups.js b/awx/ui/client/src/helpers/Groups.js deleted file mode 100644 index 8e7dcfc7ad..0000000000 --- a/awx/ui/client/src/helpers/Groups.js +++ /dev/null @@ -1,1047 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -'use strict'; - -/** - * @ngdoc function - * @name helpers.function:Groups - * @description inventory tree widget add/edit/delete -*/ - -import listGenerator from '../shared/list-generator/main'; - -export default -angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'GroupListDefinition', listGenerator.name, 'GroupsHelper', 'InventoryHelper', 'SelectionHelper', - 'JobSubmissionHelper', 'PromptDialog', 'CredentialsListDefinition', - 'InventoryStatusDefinition', 'VariablesHelper', 'SchedulesListDefinition', 'StandardOutHelper', - 'SchedulesHelper' -]) - -/** - * - * Lookup options for group source and build an array of drop-down choices - * - */ -.factory('GetSourceTypeOptions', ['Rest', 'ProcessErrors', 'GetBasePath', - function (Rest, ProcessErrors, GetBasePath) { - return function (params) { - var scope = params.scope, - variable = params.variable; - - if (scope[variable] === undefined) { - scope[variable] = []; - Rest.setUrl(GetBasePath('inventory_sources')); - Rest.options() - .success(function (data) { - var i, choices = data.actions.GET.source.choices; - for (i = 0; i < choices.length; i++) { - if (choices[i][0] !== 'file') { - scope[variable].push({ - label: choices[i][1], - value: choices[i][0] - }); - } - } - scope.cloudCredentialRequired = false; - scope.$emit('sourceTypeOptionsReady'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve options for inventory_sources.source. OPTIONS status: ' + status - }); - }); - } - }; - } -]) - -/** - * - * TODO: Document - * - */ -.factory('ViewUpdateStatus', ['$state', 'Rest', 'ProcessErrors', 'GetBasePath', 'Alert', 'Wait', 'Empty', 'Find', - function ($state, Rest, ProcessErrors, GetBasePath, Alert, Wait, Empty, Find) { - return function (params) { - - var scope = params.scope, - group_id = params.group_id, - group = Find({ list: scope.groups, key: 'id', val: group_id }); - - if (scope.removeSourceReady) { - scope.removeSourceReady(); - } - scope.removeSourceReady = scope.$on('SourceReady', function(e, source) { - - // Get the ID from the correct summary field - var update_id = (source.summary_fields.current_update) ? source.summary_fields.current_update.id : source.summary_fields.last_update.id; - - $state.go('inventorySyncStdout', {id: update_id}); - - }); - - if (group) { - if (Empty(group.source)) { - // do nothing - } else if (Empty(group.status) || group.status === "never updated") { - Alert('No Status Available', '

An inventory sync has not been performed for the selected group. Start the process by ' + - 'clicking the button.
', 'alert-info', null, null, null, null, true); - } else { - Wait('start'); - Rest.setUrl(group.related.inventory_source); - Rest.get() - .success(function (data) { - scope.$emit('SourceReady', data); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source + - ' GET returned status: ' + status }); - }); - } - } - - }; - } -]) - -/** - * - * TODO: Document - * - */ -.factory('GetHostsStatusMsg', [ - function () { - return function (params) { - - var active_failures = params.active_failures, - total_hosts = params.total_hosts, - tip, failures, html_class; - - // Return values for use on host status indicator - - if (active_failures > 0) { - tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + active_failures + ' with failed jobs.'; - html_class = 'error'; - failures = true; - } else { - failures = false; - if (total_hosts === 0) { - // no hosts - tip = "Contains 0 hosts."; - html_class = 'none'; - } else { - // many hosts with 0 failures - tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. No job failures'; - html_class = 'success'; - } - } - - return { - tooltip: tip, - failures: failures, - 'class': html_class - }; - }; -} -]) - -/** - * - * TODO: Document - * - */ -.factory('GetSyncStatusMsg', [ 'Empty', - function (Empty) { - return function (params) { - - var status = params.status, - source = params.source, - has_inventory_sources = params.has_inventory_sources, - launch_class = '', - launch_tip = 'Start sync process', - schedule_tip = 'Schedule future inventory syncs', - stat, stat_class, status_tip; - - stat = status; - stat_class = stat; - - switch (status) { - case 'never updated': - stat = 'never'; - stat_class = 'na'; - status_tip = 'Sync not performed. Click to start it now.'; - break; - case 'none': - case 'ok': - case '': - launch_class = 'btn-disabled'; - stat = 'n/a'; - stat_class = 'na'; - status_tip = 'Cloud source not configured. Click to update.'; - launch_tip = 'Cloud source not configured.'; - break; - case 'canceled': - status_tip = 'Sync canceled. Click to view log.'; - break; - case 'failed': - status_tip = 'Sync failed. Click to view log.'; - break; - case 'successful': - status_tip = 'Sync completed. Click to view log.'; - break; - case 'pending': - status_tip = 'Sync pending.'; - launch_class = "btn-disabled"; - launch_tip = "Sync pending"; - break; - case 'updating': - case 'running': - launch_class = "btn-disabled"; - launch_tip = "Sync running"; - status_tip = "Sync running. Click to view log."; - break; - } - - if (has_inventory_sources && Empty(source)) { - // parent has a source, therefore this group should not have a source - launch_class = "btn-disabled"; - status_tip = 'Managed by an external cloud source.'; - launch_tip = 'Can only be updated by running a sync on the parent group.'; - } - - if (has_inventory_sources === false && Empty(source)) { - launch_class = 'btn-disabled'; - status_tip = 'Cloud source not configured. Click to update.'; - launch_tip = 'Cloud source not configured.'; - } - - return { - "class": stat_class, - "tooltip": status_tip, - "status": stat, - "launch_class": launch_class, - "launch_tip": launch_tip, - "schedule_tip": schedule_tip - }; - }; - } -]) - -/** - * - * Cancel a pending or running inventory sync - * - */ -.factory('GroupsCancelUpdate', ['Empty', 'Rest', 'ProcessErrors', 'Alert', 'Wait', 'Find', - function (Empty, Rest, ProcessErrors, Alert, Wait, Find) { - return function (params) { - - var scope = params.scope, - id = params.id, - group = params.group; - - if (scope.removeCancelUpdate) { - scope.removeCancelUpdate(); - } - scope.removeCancelUpdate = scope.$on('CancelUpdate', function (e, url) { - // Cancel the update process - Rest.setUrl(url); - Rest.post() - .success(function () { - Wait('stop'); - //Alert('Inventory Sync Cancelled', 'Request to cancel the sync process was submitted to the task manger. ' + - // 'Click the button to monitor the status.', 'alert-info'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. POST status: ' + status - }); - }); - }); - - if (scope.removeCheckCancel) { - scope.removeCheckCancel(); - } - scope.removeCheckCancel = scope.$on('CheckCancel', function (e, last_update, current_update) { - // Check that we have access to cancelling an update - var url = (current_update) ? current_update : last_update; - url += 'cancel/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - if (data.can_cancel) { - scope.$emit('CancelUpdate', url); - //} else { - // Wait('stop'); - // Alert('Cancel Inventory Sync', 'The sync process completed. Click the button to view ' + - // 'the latest status.', 'alert-info'); - } - else { - Wait('stop'); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET status: ' + status - }); - }); - }); - - // Cancel the update process - if (Empty(group)) { - group = Find({ list: scope.groups, key: 'id', val: id }); - scope.selected_group_id = group.id; - } - - if (group && (group.status === 'running' || group.status === 'pending')) { - // We found the group, and there is a running update - Wait('start'); - Rest.setUrl(group.related.inventory_source); - Rest.get() - .success(function (data) { - scope.$emit('CheckCancel', data.related.last_update, data.related.current_update); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + group.related.inventory_source + ' failed. GET status: ' + status - }); - }); - } - }; - } -]) - - -/** - * - * Deprecated factory that used to support /#/home/groups/ - * - */ -.factory('GroupsEdit', ['$filter', '$rootScope', '$location', '$log', '$stateParams', '$compile', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate', - 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find', - 'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SetSchedulesInnerDialogSize', 'CreateSelect2', - function ($filter, $rootScope, $location, $log, $stateParams, $compile, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, Empty, Wait, - GetChoices, UpdateGroup, SourceChange, Find, ParseVariableString, ToJSON, GroupsScheduleListInit, - SetSchedulesInnerDialogSize, CreateSelect2) { - return function (params) { - - var parent_scope = params.scope, - group_id = params.group_id, - mode = params.mode, // 'add' or 'edit' - inventory_id = params.inventory_id, - generator = GenerateForm, - group_created = false, - defaultUrl, - master = {}, - choicesReady, - modal_scope = parent_scope.$new(), - properties_scope = parent_scope.$new(), - sources_scope = parent_scope.$new(), - elem, x, y, ww, wh, maxrows, - group, - schedules_url = ''; - - if (mode === 'edit') { - defaultUrl = GetBasePath('groups') + group_id + '/'; - } - else { - defaultUrl = (group_id !== null) ? GetBasePath('groups') + group_id + '/children/' : - GetBasePath('inventory') + inventory_id + '/groups/'; - } - - $('#properties-tab').empty(); - $('#sources-tab').empty(); - $('#schedules-list').empty(); - $('#schedules-form').empty(); - $('#schedules-detail').empty(); - - elem = document.getElementById('group-modal-dialog'); - $compile(elem)(modal_scope); - - var form_scope = - generator.inject(GroupForm, { mode: mode, id: 'properties-tab', related: false, scope: properties_scope }); - var source_form_scope = - generator.inject(GroupForm, { mode: mode, id: 'sources-tab', related: false, scope: sources_scope }); - - //generator.reset(); - - GetSourceTypeOptions({ scope: sources_scope, variable: 'source_type_options' }); - sources_scope.source = GroupForm.fields.source['default']; - sources_scope.sourcePathRequired = false; - sources_scope[GroupForm.fields.source_vars.parseTypeName] = 'yaml'; - sources_scope.update_cache_timeout = 0; - properties_scope.parseType = 'yaml'; - - function waitStop() { Wait('stop'); } - - // Attempt to create the largest textarea field that will fit on the window. Minimum - // height is 6 rows, so on short windows you will see vertical scrolling - function textareaResize(textareaID) { - var textArea, formHeight, model, windowHeight, offset, rows; - textArea = $('#' + textareaID); - if (properties_scope.codeMirror) { - model = textArea.attr('ng-model'); - properties_scope[model] = properties_scope.codeMirror.getValue(); - properties_scope.codeMirror.destroy(); - } - textArea.attr('rows', 1); - formHeight = $('#group_form').height(); - windowHeight = $('#group-modal-dialog').height() - 20; //leave a margin of 20px - offset = Math.floor(windowHeight - formHeight); - rows = Math.floor(offset / 24); - rows = (rows < 6) ? 6 : rows; - textArea.attr('rows', rows); - while(rows > 6 && $('#group_form').height() > $('#group-modal-dialog').height()) { - rows--; - textArea.attr('rows', rows); - } - ParseTypeChange({ scope: properties_scope, field_id: textareaID, onReady: waitStop }); - } - - function initSourceChange() { - parent_scope.showSchedulesTab = (mode === 'edit' && sources_scope.source && sources_scope.source.value!=="manual") ? true : false; - SourceChange({ scope: sources_scope, form: GroupForm }); - } - - // Set modal dimensions based on viewport width - ww = $(document).width(); - wh = $('body').height(); - if (ww > 1199) { - // desktop - x = 675; - y = (800 > wh) ? wh - 15 : 800; - maxrows = 18; - } else if (ww <= 1199 && ww >= 768) { - x = 550; - y = (770 > wh) ? wh - 15 : 770; - maxrows = 12; - } else { - x = (ww - 20); - y = (770 > wh) ? wh - 15 : 770; - maxrows = 10; - } - - // Create the modal - $('#group-modal-dialog').dialog({ - buttons: { - 'Save': function () { - modal_scope.saveGroup(); - }, - 'Cancel': function() { - modal_scope.cancelModal(); - } - }, - modal: true, - width: x, - height: y, - autoOpen: false, - minWidth: 440, - title: (mode === 'edit') ? 'Edit Group' : 'Add Group', - closeOnEscape: false, - create: function () { - $('.ui-dialog[aria-describedby="group-modal-dialog"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).text('x'); - $('.ui-dialog[aria-describedby="group-modal-dialog"]').find('.ui-dialog-buttonset button').each(function () { - var c, h, i, l; - l = $(this).text(); - if (l === 'Cancel') { - h = "fa-times"; - c = "btn btn-default"; - i = "group-close-button"; - $(this).attr({ - 'class': c, - 'id': i - }).html(" Cancel"); - } else if (l === 'Save') { - h = "fa-check"; - c = "btn btn-primary"; - i = "group-save-button"; - $(this).attr({ - 'class': c, - 'id': i - }).html(" Save"); - } - }); - }, - resizeStop: function () { - // for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100% - var dialog = $('.ui-dialog[aria-describedby="group-modal-dialog"]'), - titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(), - buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(), - content = dialog.find('#group-modal-dialog'), - w; - content.width(dialog.width() - 28); - content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) }); - - if ($('#group_tabs .active a').text() === 'Properties') { - textareaResize('group_variables', properties_scope); - } - else if ($('#group_tabs .active a').text() === 'Schedule') { - w = $('#group_tabs').width() - 18; - $('#schedules-overlay').width(w); - $('#schedules-form-container').width(w); - SetSchedulesInnerDialogSize(); - } - }, - close: function () { - // Destroy on close - $('.tooltip').each(function () { - // Remove any lingering tooltip
elements - $(this).remove(); - }); - $('.popover').each(function () { - // remove lingering popover
elements - $(this).remove(); - }); - if (properties_scope.codeMirror) { - properties_scope.codeMirror.destroy(); - } - if (sources_scope.codeMirror) { - sources_scope.codeMirror.destroy(); - } - $('#properties-tab').empty(); - $('#sources-tab').empty(); - $('#schedules-list').empty(); - $('#schedules-form').empty(); - $('#schedules-detail').empty(); - $('#group-modal-dialog').hide(); - $('#group-modal-dialog').dialog('destroy'); - modal_scope.cancelModal(); - }, - open: function () { - function updateButtonStatus(isValid) { - $('.ui-dialog[aria-describedby="group-modal-dialog"]').find('.btn-primary').prop('disabled', !isValid); - } - form_scope.$watch('group_form.$valid', updateButtonStatus); - source_form_scope.$watch('source_form.$valid', updateButtonStatus); - $('#group_name').focus(); - Wait('stop'); - } - }); - - $('#group_tabs a[data-toggle="tab"]').on('show.bs.tab', function (e) { - if ($(e.target).text() === 'Properties') { - Wait('start'); - setTimeout(function(){ textareaResize('group_variables'); }, 300); - } - else if ($(e.target).text() === 'Source') { - if (sources_scope.source && (sources_scope.source.value === 'ec2')) { - Wait('start'); - ParseTypeChange({ scope: sources_scope, variable: 'source_vars', parse_variable: GroupForm.fields.source_vars.parseTypeName, - field_id: 'source_source_vars', onReady: waitStop }); - } else if (sources_scope.source && (sources_scope.source.value === 'vmware' || - sources_scope.source.value === 'openstack')) { - Wait('start'); - ParseTypeChange({ scope: sources_scope, variable: 'inventory_variables', parse_variable: GroupForm.fields.inventory_variables.parseTypeName, - field_id: 'source_inventory_variables', onReady: waitStop }); - } - else if (sources_scope.source && (sources_scope.source.value === 'custom')) { - Wait('start'); - ParseTypeChange({ scope: sources_scope, variable: 'extra_vars', parse_variable: GroupForm.fields.extra_vars.parseTypeName, - field_id: 'source_extra_vars', onReady: waitStop }); - } - } - else if ($(e.target).text() === 'Schedule') { - $('#schedules-overlay').hide(); - } - }); - - if (modal_scope.groupVariablesLoadedRemove) { - modal_scope.groupVariablesLoadedRemove(); - } - modal_scope.groupVariablesLoadedRemove = modal_scope.$on('groupVariablesLoaded', function () { - if (mode === 'edit' && - group.has_inventory_sources && - Empty(group.summary_fields.inventory_source.source) && - sources_scope.source && - sources_scope.source.value !== 'manual') { - modal_scope.showSourceTab = false; - } else { - modal_scope.showSourceTab = true; - } - modal_scope.showSchedulesTab = (mode === 'edit' && sources_scope.source && sources_scope.source.value!=='manual') ? true : false; - if (mode === 'edit' && modal_scope.showSourceTab) { - // the use has access to the source tab, so they may create a schedule - GroupsScheduleListInit({ scope: modal_scope, url: schedules_url }); - } - $('#group_tabs a:first').tab('show'); - Wait('start'); - $('#group-modal-dialog').dialog('open'); - setTimeout(function() { textareaResize('group_variables', properties_scope); }, 300); - }); - - // JT -- this gets called after the properties & properties variables are loaded, and is emitted from (groupLoaded) - if (modal_scope.removeLoadSourceData) { - modal_scope.removeLoadSourceData(); - } - modal_scope.removeLoadSourceData = modal_scope.$on('LoadSourceData', function () { - if (sources_scope.source_url) { - // get source data - Rest.setUrl(sources_scope.source_url); - Rest.get() - .success(function (data) { - var fld, i, j, flag, found, set, opts, list, form; - form = GroupForm; - for (fld in form.fields) { - if (fld === 'checkbox_group') { - for (i = 0; i < form.fields[fld].fields.length; i++) { - flag = form.fields[fld].fields[i]; - if (data[flag.name] !== undefined) { - sources_scope[flag.name] = data[flag.name]; - master[flag.name] = sources_scope[flag.name]; - } - } - } - if (fld === 'source') { - found = false; - data.source = (data.source === "" ) ? "manual" : data.source; - for (i = 0; i < sources_scope.source_type_options.length; i++) { - if (sources_scope.source_type_options[i].value === data.source) { - sources_scope.source = sources_scope.source_type_options[i]; - found = true; - } - } - if (!found || sources_scope.source.value === "manual") { - sources_scope.groupUpdateHide = true; - } else { - sources_scope.groupUpdateHide = false; - } - master.source = sources_scope.source; - } else if (fld === 'source_vars') { - // Parse source_vars, converting to YAML. - sources_scope.source_vars = ParseVariableString(data.source_vars); - master.source_vars = sources_scope.variables; - } - else if(fld === "inventory_script"){ - // the API stores it as 'source_script', we call it inventory_script - data.summary_fields.inventory_script = data.summary_fields.source_script; - sources_scope.inventory_script = data.source_script; - master.inventory_script = sources_scope.inventory_script; - } else if (fld === "source_regions") { - if (data[fld] === "") { - sources_scope[fld] = data[fld]; - master[fld] = sources_scope[fld]; - } else { - sources_scope[fld] = data[fld].split(","); - master[fld] = sources_scope[fld]; - } - } else if (data[fld] !== undefined) { - sources_scope[fld] = data[fld]; - master[fld] = sources_scope[fld]; - } - - if (form.fields[fld].sourceModel && data.summary_fields && - data.summary_fields[form.fields[fld].sourceModel]) { - sources_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]; - } - } - - initSourceChange(); - - if (data.source_regions) { - if (data.source === 'ec2' || - data.source === 'rax' || - data.source === 'gce' || - data.source === 'azure') { - if (data.source === 'ec2') { - set = sources_scope.ec2_regions; - } else if (data.source === 'rax') { - set = sources_scope.rax_regions; - } else if (data.source === 'gce') { - set = sources_scope.gce_regions; - } else if (data.source === 'azure') { - set = sources_scope.azure_regions; - } - opts = []; - list = data.source_regions.split(','); - for (i = 0; i < list.length; i++) { - for (j = 0; j < set.length; j++) { - if (list[i] === set[j].value) { - opts.push({ - id: set[j].value, - text: set[j].label - }); - } - } - } - master.source_regions = opts; - CreateSelect2({ - element: "#source_source_regions", - opts: opts - }); - - } - } else { - // If empty, default to all - master.source_regions = [{ - id: 'all', - text: 'All' - }]; - } - if (data.group_by && data.source === 'ec2') { - set = sources_scope.ec2_group_by; - opts = []; - list = data.group_by.split(','); - for (i = 0; i < list.length; i++) { - for (j = 0; j < set.length; j++) { - if (list[i] === set[j].value) { - opts.push({ - id: set[j].value, - text: set[j].label - }); - } - } - } - master.group_by = opts; - CreateSelect2({ - element: "#source_group_by", - opts: opts - }); - } - sources_scope.group_update_url = data.related.update; - modal_scope.$emit('groupVariablesLoaded'); // JT-- "groupVariablesLoaded" is where the schedule info is loaded, so I make a call after the sources_scope.source has been loaded - //Wait('stop'); - }) - .error(function (data, status) { - sources_scope.source = ""; - ProcessErrors(modal_scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve inventory source. GET status: ' + status }); - }); - } - else { - modal_scope.$emit('groupVariablesLoaded'); // JT-- "groupVariablesLoaded" is where the schedule info is loaded, so I make a call after the sources_scope.source has been loaded - } - }); - - if (sources_scope.removeScopeSourceTypeOptionsReady) { - sources_scope.removeScopeSourceTypeOptionsReady(); - } - sources_scope.removeScopeSourceTypeOptionsReady = sources_scope.$on('sourceTypeOptionsReady', function() { - if (mode === 'add') { - sources_scope.source = Find({ - list: sources_scope.source_type_options, - key: 'value', - val: '' - }); - modal_scope.showSchedulesTab = false; - } - }); - - if (modal_scope.removeChoicesComplete) { - modal_scope.removeChoicesComplete(); - } - modal_scope.removeChoicesComplete = modal_scope.$on('choicesCompleteGroup', function () { - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl); - Rest.get() - .success(function (data) { - group = data; - for (var fld in GroupForm.fields) { - if (data[fld]) { - properties_scope[fld] = data[fld]; - master[fld] = properties_scope[fld]; - } - } - schedules_url = data.related.inventory_source + 'schedules/'; - properties_scope.variable_url = data.related.variable_data; - sources_scope.source_url = data.related.inventory_source; - modal_scope.$emit('LoadSourceData'); - }) - .error(function (data, status) { - ProcessErrors(modal_scope, data, status, { hdr: 'Error!', - msg: 'Failed to retrieve group: ' + defaultUrl + '. GET status: ' + status }); - }); - }); - - choicesReady = 0; - - if (sources_scope.removeChoicesReady) { - sources_scope.removeChoicesReady(); - } - sources_scope.removeChoicesReady = sources_scope.$on('choicesReadyGroup', function () { - CreateSelect2({ - element: '#source_source', - multiple: false - }); - choicesReady++; - if (choicesReady === 2) { - if (mode === 'edit') { - modal_scope.$emit('choicesCompleteGroup'); - } - else { - properties_scope.variables = "---"; - master.variables = properties_scope.variables; - modal_scope.$emit('groupVariablesLoaded'); - } - } - }); - - // Load options for source regions - GetChoices({ - scope: sources_scope, - url: GetBasePath('inventory_sources'), - field: 'source_regions', - variable: 'rax_regions', - choice_name: 'rax_region_choices', - callback: 'choicesReadyGroup' - }); - - GetChoices({ - scope: sources_scope, - url: GetBasePath('inventory_sources'), - field: 'source_regions', - variable: 'ec2_regions', - choice_name: 'ec2_region_choices', - callback: 'choicesReadyGroup' - }); - - GetChoices({ - scope: sources_scope, - url: GetBasePath('inventory_sources'), - field: 'source_regions', - variable: 'gce_regions', - choice_name: 'gce_region_choices', - callback: 'choicesReadyGroup' - }); - - GetChoices({ - scope: sources_scope, - url: GetBasePath('inventory_sources'), - field: 'source_regions', - variable: 'azure_regions', - choice_name: 'azure_region_choices', - callback: 'choicesReadyGroup' - }); - - // Load options for group_by - GetChoices({ - scope: sources_scope, - url: GetBasePath('inventory_sources'), - field: 'group_by', - variable: 'ec2_group_by', - choice_name: 'ec2_group_by_choices', - callback: 'choicesReadyGroup' - }); - - Wait('start'); - - if (parent_scope.removeAddTreeRefreshed) { - parent_scope.removeAddTreeRefreshed(); - } - parent_scope.removeAddTreeRefreshed = parent_scope.$on('GroupTreeRefreshed', function() { - // Clean up - Wait('stop'); - - // @issue: OLD SEARCH - // if (modal_scope.searchCleanUp) { - // modal_scope.searchCleanup(); - // } - - try { - $('#group-modal-dialog').dialog('close'); - } - catch(e) { - // ignore - } - }); - - if (modal_scope.removeSaveComplete) { - modal_scope.removeSaveComplete(); - } - modal_scope.removeSaveComplete = modal_scope.$on('SaveComplete', function (e, error) { - if (!error) { - modal_scope.cancelModal(); - } - }); - - if (modal_scope.removeFormSaveSuccess) { - modal_scope.removeFormSaveSuccess(); - } - modal_scope.removeFormSaveSuccess = modal_scope.$on('formSaveSuccess', function () { - - // Source data gets stored separately from the group. Validate and store Source - // related fields, then call SaveComplete to wrap things up. - - var parseError = false, - regions, r, i, - group_by, - data = { - group: group_id, - source: ((sources_scope.source && sources_scope.source.value!=='manual') ? sources_scope.source.value : ''), - source_path: sources_scope.source_path, - credential: sources_scope.credential, - overwrite: sources_scope.overwrite, - overwrite_vars: sources_scope.overwrite_vars, - source_script: sources_scope.inventory_script, - update_on_launch: sources_scope.update_on_launch, - update_cache_timeout: (sources_scope.update_cache_timeout || 0) - }; - - // Create a string out of selected list of regions - if(sources_scope.source_regions){ - regions = $('#source_source_regions').select2("data"); - r = []; - for (i = 0; i < regions.length; i++) { - r.push(regions[i].id); - } - data.source_regions = r.join(); - } - - if (sources_scope.source && (sources_scope.source.value === 'ec2')) { - data.instance_filters = sources_scope.instance_filters; - // Create a string out of selected list of regions - group_by = $('#source_group_by').select2("data"); - r = []; - for (i = 0; i < group_by.length; i++) { - r.push(group_by[i].id); - } - data.group_by = r.join(); - } - - if (sources_scope.source && (sources_scope.source.value === 'ec2' )) { - // for ec2, validate variable data - data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.source_vars, true); - } - - if (sources_scope.source && (sources_scope.source.value === 'custom')) { - data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.extra_vars, true); - } - - if (sources_scope.source && (sources_scope.source.value === 'vmware' || - sources_scope.source.value === 'openstack')) { - data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true); - } - - // the API doesn't expect the credential to be passed with a custom inv script - if(sources_scope.source && sources_scope.source.value === 'custom'){ - delete(data.credential); - } - - if (!parseError) { - Rest.setUrl(sources_scope.source_url); - Rest.put(data) - .success(function () { - modal_scope.$emit('SaveComplete', false); - }) - .error(function (data, status) { - $('#group_tabs a:eq(1)').tab('show'); - ProcessErrors(sources_scope, data, status, GroupForm, { hdr: 'Error!', - msg: 'Failed to update group inventory source. PUT status: ' + status }); - }); - } - }); - - // Cancel - modal_scope.cancelModal = function () { - try { - $('#group-modal-dialog').dialog('close'); - } - catch(e) { - //ignore - } - - // @issue: OLD SEARCH - // if (modal_scope.searchCleanup) { - // modal_scope.searchCleanup(); - // } - // if (parent_scope.restoreSearch) { - // parent_scope.restoreSearch(); - // } - // else { - // Wait('stop'); - // } - - Wait('stop'); - }; - - // Save - modal_scope.saveGroup = function () { - Wait('start'); - var fld, data, json_data; - - try { - - json_data = ToJSON(properties_scope.parseType, properties_scope.variables, true); - - data = {}; - for (fld in GroupForm.fields) { - data[fld] = properties_scope[fld]; - } - - data.inventory = inventory_id; - - Rest.setUrl(defaultUrl); - if (mode === 'edit' || (mode === 'add' && group_created)) { - Rest.put(data) - .success(function () { - modal_scope.$emit('formSaveSuccess'); - }) - .error(function (data, status) { - $('#group_tabs a:eq(0)').tab('show'); - ProcessErrors(properties_scope, data, status, GroupForm, { hdr: 'Error!', - msg: 'Failed to update group: ' + group_id + '. PUT status: ' + status - }); - }); - } - else { - Rest.post(data) - .success(function (data) { - group_created = true; - group_id = data.id; - sources_scope.source_url = data.related.inventory_source; - modal_scope.$emit('formSaveSuccess'); - }) - .error(function (data, status) { - $('#group_tabs a:eq(0)').tab('show'); - ProcessErrors(properties_scope, data, status, GroupForm, { hdr: 'Error!', - msg: 'Failed to create group: ' + group_id + '. POST status: ' + status - }); - }); - } - } - catch(e) { - // ignore. ToJSON will have already alerted the user - } - }; - - // Start the update process - modal_scope.updateGroup = function () { - if (sources_scope.source === "manual" || sources_scope.source === null) { - Alert('Missing Configuration', 'The selected group is not configured for updates. You must first edit the group, provide Source settings, ' + - 'and then run an update.', 'alert-info'); - } else if (sources_scope.status === 'updating') { - Alert('Update in Progress', '
The inventory update process is currently running for group ' + - $filter('sanitize')(sources_scope.summary_fields.group.name) + '. Use the Refresh button to monitor the status.
', 'alert-info', null, null, null, null, true); - } else { - InventoryUpdate({ - scope: parent_scope, - group_id: group_id, - url: properties_scope.group_update_url, - group_name: properties_scope.name, - group_source: sources_scope.source.value - }); - } - }; - - // Change the lookup and regions when the source changes - sources_scope.sourceChange = function () { - sources_scope.credential_name = ""; - sources_scope.credential = ""; - if (sources_scope.credential_name_api_error) { - delete sources_scope.credential_name_api_error; - } - initSourceChange(); - }; - - }; - } -]); diff --git a/awx/ui/client/src/helpers/Hosts.js b/awx/ui/client/src/helpers/Hosts.js deleted file mode 100644 index 2cb4aa36a5..0000000000 --- a/awx/ui/client/src/helpers/Hosts.js +++ /dev/null @@ -1,465 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -/* jshint loopfunc: true */ -/** - * @ngdoc function - * @name helpers.function:Hosts - * @description Routines that handle host add/edit/delete on the Inventory detail page. - */ - -'use strict'; - -import listGenerator from '../shared/list-generator/main'; - -export default -angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'HostListDefinition', - listGenerator.name, 'HostsHelper', - 'InventoryHelper', 'InventoryFormDefinition', 'SelectionHelper', - 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'StandardOutHelper', - 'GroupListDefinition' -]) - -.factory('SetEnabledMsg', [ function() { - return function(host) { - if (host.has_inventory_sources) { - // Inventory sync managed, so not clickable - host.enabledToolTip = (host.enabled) ? 'Host is available' : 'Host is not available'; - } - else { - // Clickable - host.enabledToolTip = (host.enabled) ? 'Host is available. Click to toggle.' : 'Host is not available. Click to toggle.'; - } - }; -}]) - -.factory('SetHostStatus', ['SetEnabledMsg', function(SetEnabledMsg) { - return function(host) { - // Set status related fields on a host object - host.activeFailuresLink = '/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + host.inventory + - '&host_name=' + encodeURI(host.name); - if (host.has_active_failures === true) { - host.badgeToolTip = 'Most recent job failed. Click to view jobs.'; - host.active_failures = 'failed'; - } - else if (host.has_active_failures === false && host.last_job === null) { - host.has_active_failures = 'none'; - host.badgeToolTip = "No job data available."; - host.active_failures = 'n/a'; - } - else if (host.has_active_failures === false && host.last_job !== null) { - host.badgeToolTip = "Most recent job successful. Click to view jobs."; - host.active_failures = 'success'; - } - - host.enabled_flag = host.enabled; - SetEnabledMsg(host); - - }; -}]) - -.factory('SetStatus', ['$filter', 'SetEnabledMsg', 'Empty', function($filter, SetEnabledMsg, Empty) { - return function(params) { - - var scope = params.scope, - host = params.host, - i, html, title; - - function ellipsis(a) { - if (a.length > 25) { - return a.substr(0,25) + '...'; - } - return a; - } - - function noRecentJobs() { - title = 'No job data'; - html = "

No recent job data available for this host.

\n"; - } - - function setMsg(host) { - var j, job, jobs; - - if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) { - if (host.has_active_failures === true) { - host.badgeToolTip = 'Most recent job failed. Click to view jobs.'; - host.active_failures = 'error'; - } - else { - host.badgeToolTip = "Most recent job successful. Click to view jobs."; - host.active_failures = 'successful'; - } - if (host.summary_fields.recent_jobs.length > 0) { - // build html table of job status info - jobs = host.summary_fields.recent_jobs.sort( - function(a,b) { - // reverse numerical order - return -1 * (a - b); - }); - title = "Recent Jobs"; - html = "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - for (j=0; j < jobs.length; j++) { - job = jobs[j]; - html += "\n"; - - // SmartStatus-tooltips are named --success whereas icon-job uses successful - var iconStatus = (job.status === 'successful') ? 'success' : 'failed'; - - html += "\n"; - - html += "\n"; - - html += "\n"; - - html += "\n"; - } - html += "\n"; - html += "
StatusFinishedName
" + ($filter('longDate')(job.finished)).replace(/ /,'
') + "
" + ellipsis(job.name) + "
\n"; - } - else { - noRecentJobs(); - } - } - else if (host.has_active_failures === false && host.last_job === null) { - host.badgeToolTip = "No job data available."; - host.active_failures = 'none'; - noRecentJobs(); - } - host.job_status_html = html; - host.job_status_title = title; - } - - if (!Empty(host)) { - // update single host - setMsg(host); - SetEnabledMsg(host); - } - else { - // update all hosts - for (i=0; i < scope.hosts.length; i++) { - setMsg(scope.hosts[i]); - SetEnabledMsg(scope.hosts[i]); - } - } - - }; -}]) - -.factory('HostsReload', [ '$stateParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'Wait', - 'SetHostStatus', 'SetStatus', 'ApplyEllipsis', - function($stateParams, Empty, InventoryHosts, GetBasePath, Wait, SetHostStatus, SetStatus, - ApplyEllipsis) { - return function(params) { - - var scope = params.scope, - parent_scope = params.parent_scope; - - // @issue: OLD SEARCH - // var list = InventoryHosts, - // group_id = params.group_id, - // inventory_id = params.inventory_id; - // pageSize = (params.pageSize) ? params.pageSize : 20, - // - // url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' : - // GetBasePath('inventory') + inventory_id + '/hosts/'; - - // @issue: OLD SEARCH - // scope.search_place_holder='Search ' + scope.selected_group_name; - - if (scope.removeHostsReloadPostRefresh) { - scope.removeHostsReloadPostRefresh(); - } - scope.removeHostsReloadPostRefresh = scope.$on('PostRefresh', function(e, set) { - if (set === 'hosts') { - for (var i=0; i < scope.hosts.length; i++) { - //Set tooltip for host enabled flag - scope.hosts[i].enabled_flag = scope.hosts[i].enabled; - } - SetStatus({ scope: scope }); - setTimeout(function() { ApplyEllipsis('#hosts_table .host-name a'); }, 2500); - Wait('stop'); - if (parent_scope) { - parent_scope.$emit('HostReloadComplete'); - } - } - }); - - // @issue: OLD SEARCH - // SearchInit({ scope: scope, set: 'hosts', list: list, url: url }); - // PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize }); - // - // if ($stateParams.host_name) { - // scope[list.iterator + 'InputDisable'] = false; - // scope[list.iterator + 'SearchValue'] = $stateParams.host_name; - // scope[list.iterator + 'SearchField'] = 'name'; - // scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label; - // scope[list.iterator + 'SearchSelectValue'] = null; - // } - // - // if (scope.show_failures) { - // scope[list.iterator + 'InputDisable'] = true; - // scope[list.iterator + 'SearchValue'] = 'true'; - // scope[list.iterator + 'SearchField'] = 'has_active_failures'; - // scope[list.iterator + 'SearchFieldLabel'] = list.fields.has_active_failures.label; - // scope[list.iterator + 'SearchSelectValue'] = { value: 1 }; - // } - // scope.search(list.iterator, null, true); - }; - }]) - -.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList', -function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList) { -return function(params) { - - var host_id = params.host_id, - group_scope = params.group_scope, - parent_scope = params.host_scope, - parent_group = group_scope.selected_group_id, - scope = parent_scope.$new(), - buttonSet, url, host; - - buttonSet = [{ - label: "Cancel", - onClick: function() { - scope.cancel(); - }, - icon: "fa-times", - "class": "btn btn-default", - "id": "host-copy-cancel-button" - },{ - label: "OK", - onClick: function() { - scope.performCopy(); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "host-copy-ok-button" - }]; - - if (scope.removeHostCopyPostRefresh) { - scope.removeHostCopyPostRefresh(); - } - scope.removeHostCopyPostRefresh = scope.$on('PostRefresh', function() { - scope.copy_groups.forEach(function(row, i) { - scope.copy_groups[i].checked = '0'; - }); - Wait('stop'); - $('#host-copy-dialog').dialog('open'); - $('#host-copy-ok-button').attr('disabled','disabled'); - - // prevent backspace from navigation when not in input or textarea field - $(document).on("keydown", function (e) { - if (e.which === 8 && !$(e.target).is('input[type="text"], textarea')) { - e.preventDefault(); - } - }); - }); - - if (scope.removeHostCopyDialogReady) { - scope.removeHostCopyDialogReady(); - } - scope.removeCopyDialogReady = scope.$on('HostCopyDialogReady', function() { - // @issue: OLD SEARCH - // var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/'; - - GenerateList.inject(GroupList, { - mode: 'lookup', - id: 'copy-host-select-container', - scope: scope - //, - //instructions: instructions - }); - - // @issue: OLD SEARCH - // SearchInit({ - // scope: scope, - // set: GroupList.name, - // list: GroupList, - // url: url - // }); - // PaginateInit({ - // scope: scope, - // list: GroupList, - // url: url, - // mode: 'lookup' - // }); - // scope.search(GroupList.iterator, null, true, false); - }); - - if (scope.removeShowDialog) { - scope.removeShowDialog(); - } - scope.removeShowDialog = scope.$on('ShowDialog', function() { - var d; - scope.name = host.name; - scope.copy_choice = "copy"; - d = angular.element(document.getElementById('host-copy-dialog')); - $compile(d)(scope); - CreateDialog({ - id: 'host-copy-dialog', - scope: scope, - buttons: buttonSet, - width: 650, - height: 650, - minWidth: 600, - title: 'Copy or Move Host', - callback: 'HostCopyDialogReady', - onClose: function() { - scope.cancel(); - } - }); - }); - - Wait('start'); - - url = GetBasePath('hosts') + host_id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - host = data; - scope.$emit('ShowDialog'); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status }); - }); - - - scope.cancel = function() { - $(document).off("keydown"); - try { - $('#host-copy-dialog').dialog('close'); - } - catch(e) { - // ignore - } - - // @issue: OLD SEARCH - // scope.searchCleanup(); - - // @issue: OLD SEARCH - // group_scope.restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists - scope.$destroy(); - }; - - scope['toggle_' + GroupList.iterator] = function (id) { - var count = 0, - list = GroupList; - scope[list.name].forEach( function(row, i) { - if (row.id === id) { - if (row.checked) { - scope[list.name][i].success_class = 'success'; - } - else { - scope[list.name][i].success_class = ''; - } - } else { - scope[list.name][i].checked = 0; - scope[list.name][i].success_class = ''; - } - }); - // Check if any rows are checked - scope[list.name].forEach(function(row) { - if (row.checked) { - count++; - } - }); - if (count === 0) { - $('#host-copy-ok-button').attr('disabled','disabled'); - } - else { - $('#host-copy-ok-button').removeAttr('disabled'); - } - }; - - scope.performCopy = function() { - var list = GroupList, - target, - url; - - Wait('start'); - - if (scope.use_root_group) { - target = null; - } - else { - scope[list.name].every(function(row) { - if (row.checked === 1) { - target = row; - return false; - } - return true; - }); - } - - if (scope.copy_choice === 'move') { - // Respond to move - - // disassociate the host from the original parent - if (scope.removeHostRemove) { - scope.removeHostRemove(); - } - scope.removeHostRemove = scope.$on('RemoveHost', function () { - if (parent_group > 0) { - // Only remove a host from a parent when the parent is a group and not the inventory root - url = GetBasePath('groups') + parent_group + '/hosts/'; - Rest.setUrl(url); - Rest.post({ id: host.id, disassociate: 1 }) - .success(function () { - scope.cancel(); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to remove ' + host.name + ' from group ' + parent_group + '. POST returned: ' + status }); - }); - } else { - scope.cancel(); - } - }); - - // add the new host to the target - url = GetBasePath('groups') + target.id + '/hosts/'; - Rest.setUrl(url); - Rest.post(host) - .success(function () { - scope.$emit('RemoveHost'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status }); - }); - } - else { - // Respond to copy by adding the new host to the target - url = GetBasePath('groups') + target.id + '/hosts/'; - Rest.setUrl(url); - Rest.post(host) - .success(function () { - scope.cancel(); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status - }); - }); - } - }; - - -}; -}]); diff --git a/awx/ui/client/src/helpers/JobSubmission.js b/awx/ui/client/src/helpers/JobSubmission.js deleted file mode 100644 index c4fd62434d..0000000000 --- a/awx/ui/client/src/helpers/JobSubmission.js +++ /dev/null @@ -1,337 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -'use strict'; - -export default -angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition', -'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition']) - -.factory('CreateLaunchDialog', ['$compile', 'CreateDialog', 'Wait', 'ParseTypeChange', -function($compile, CreateDialog, Wait, ParseTypeChange) { - return function(params) { - var buttons, - scope = params.scope, - html = params.html, - // job_launch_data = {}, - callback = params.callback || 'PlaybookLaunchFinished', - // url = params.url, - e; - - // html+='
job_launch_form.$valid = {{job_launch_form.$valid}}
'; - html+=''; - $('#password-modal').empty().html(html); - $('#password-modal').find('#job_extra_vars').before(scope.helpContainer); - e = angular.element(document.getElementById('password-modal')); - $compile(e)(scope); - - if(scope.prompt_for_vars===true){ - ParseTypeChange({ scope: scope, field_id: 'job_extra_vars' , variable: "extra_vars"}); - } - - buttons = [{ - label: "Cancel", - onClick: function() { - $('#password-modal').dialog('close'); - // scope.$emit('CancelJob'); - // scope.$destroy(); - }, - icon: "fa-times", - "class": "btn btn-default", - "id": "password-cancel-button" - },{ - label: "Launch", - onClick: function() { - scope.$emit(callback); - $('#password-modal').dialog('close'); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "password-accept-button" - }]; - - CreateDialog({ - id: 'password-modal', - scope: scope, - buttons: buttons, - width: 620, - height: 700, //(scope.passwords.length > 1) ? 700 : 500, - minWidth: 500, - title: 'Launch Configuration', - callback: 'DialogReady', - onOpen: function(){ - Wait('stop'); - } - }); - - if (scope.removeDialogReady) { - scope.removeDialogReady(); - } - scope.removeDialogReady = scope.$on('DialogReady', function() { - $('#password-modal').dialog('open'); - $('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' ); - e = angular.element(document.getElementById('password-accept-button')); - $compile(e)(scope); - }); - }; - - }]) - - .factory('PromptForPasswords', ['CredentialForm', - function(CredentialForm) { - return function(params) { - var scope = params.scope, - callback = params.callback || 'PasswordsAccepted', - url = params.url, - form = CredentialForm, - fld, field, - html=params.html || ""; - - scope.passwords = params.passwords; - - html += "
Launching this job requires the passwords listed below. Enter and confirm each password before continuing.
\n"; - - scope.passwords.forEach(function(password) { - // Prompt for password - field = form.fields[password]; - fld = password; - scope[fld] = ''; - html += "
\n"; - html += "\n"; - html += "Please enter a password.
\n"; - html += "
\n"; - html += "
\n"; - - // Add the related confirm field - if (field.associated) { - fld = field.associated; - field = form.fields[field.associated]; - scope[fld] = ''; - html += "
\n"; - html += "\n"; - html += "Please confirm the password.\n"; - html += (field.awPassMatch) ? "This value does not match the password you entered previously. Please confirm that password.
\n" : ""; - html += "
\n"; - html += "
\n"; - } - }); - - scope.$emit(callback, html, url); - - // Password change - scope.clearPWConfirm = function (fld) { - // If password value changes, make sure password_confirm must be re-entered - scope[fld] = ''; - scope.job_launch_form[fld].$setValidity('awpassmatch', false); - scope.checkStatus(); - }; - - scope.checkStatus = function() { - if (!scope.job_launch_form.$invalid) { - $('#password-accept-button').removeAttr('disabled'); - } - else { - $('#password-accept-button').attr({ "disabled": "disabled" }); - } - }; - }; - }]) - - .factory('CheckPasswords', ['Rest', 'GetBasePath', 'ProcessErrors', 'Empty', - function(Rest, GetBasePath, ProcessErrors, Empty) { - return function(params) { - var scope = params.scope, - callback = params.callback, - credential = params.credential; - - var passwords = []; - if (!Empty(credential)) { - Rest.setUrl(GetBasePath('credentials')+credential); - Rest.get() - .success(function (data) { - if(data.kind === "ssh"){ - if(data.password === "ASK" ){ - passwords.push("ssh_password"); - } - if(data.ssh_key_unlock === "ASK"){ - passwords.push("ssh_key_unlock"); - } - if(data.become_password === "ASK"){ - passwords.push("become_password"); - } - if(data.vault_password === "ASK"){ - passwords.push("vault_password"); - } - } - scope.$emit(callback, passwords); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job template details. GET returned status: ' + status }); - }); - } - - }; - }]) - - // Submit SCM Update request - .factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait', - function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) { - return function (params) { - var scope = params.scope, - project_id = params.project_id, - url = GetBasePath('projects') + project_id + '/update/', - project; - - if (scope.removeUpdateSubmitted) { - scope.removeUpdateSubmitted(); - } - scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() { - // Refresh the project list after update request submitted - Wait('stop'); - if (/\d$/.test($location.path())) { - //Request submitted from projects/N page. Navigate back to the list so user can see status - $location.path('/projects'); - } - if (scope.socketStatus === 'error') { - Alert('Update Started', '
The request to start the SCM update process was submitted. ' + - 'To monitor the update status, refresh the page by clicking the button.
', 'alert-info', null, null, null, null, true); - if (scope.refresh) { - scope.refresh(); - } - } - }); - - if (scope.removePromptForPasswords) { - scope.removePromptForPasswords(); - } - scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { - PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' }); - }); - - if (scope.removeStartTheUpdate) { - scope.removeStartTheUpdate(); - } - scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { - LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); - }); - - // Check to see if we have permission to perform the update and if any passwords are needed - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - project = data; - if (project.can_update) { - if (project.passwords_needed_to_updated) { - Wait('stop'); - scope.$emit('PromptForPasswords'); - } - else { - scope.$emit('StartTheUpdate', {}); - } - } - else { - Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', - 'alert-danger'); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to lookup project ' + url + ' GET returned: ' + status }); - }); - }; - } - ]) - - // Submit Inventory Update request - .factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait', - function (PromptForPasswords, LaunchJob, Rest, GetBasePath, ProcessErrors, Alert, Wait) { - return function (params) { - - var scope = params.scope, - url = params.url, - inventory_source; - - if (scope.removeUpdateSubmitted) { - scope.removeUpdateSubmitted(); - } - scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () { - Wait('stop'); - if (scope.socketStatus === 'error') { - Alert('Sync Started', '
The request to start the inventory sync process was submitted. ' + - 'To monitor the status refresh the page by clicking the button.
', 'alert-info', null, null, null, null, true); - if (scope.refreshGroups) { - // inventory detail page - scope.refreshGroups(); - } - else if (scope.refresh) { - scope.refresh(); - } - } - }); - - if (scope.removePromptForPasswords) { - scope.removePromptForPasswords(); - } - scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { - PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' }); - }); - - if (scope.removeStartTheUpdate) { - scope.removeStartTheUpdate(); - } - scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { - LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); - }); - - // Check to see if we have permission to perform the update and if any passwords are needed - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - inventory_source = data; - if (data.can_update) { - if (data.passwords_needed_to_update) { - Wait('stop'); - scope.$emit('PromptForPasswords'); - } - else { - scope.$emit('StartTheUpdate', {}); - } - } else { - Wait('stop'); - Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.', - 'alert-danger'); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status }); - }); - }; - } - ]); diff --git a/awx/ui/client/src/helpers/JobTemplates.js b/awx/ui/client/src/helpers/JobTemplates.js deleted file mode 100644 index 35d80e78f6..0000000000 --- a/awx/ui/client/src/helpers/JobTemplates.js +++ /dev/null @@ -1,173 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name helpers.function:JobTemplatesHelper - * @description Routines shared by job related controllers - */ - -export default -angular.module('JobTemplatesHelper', ['Utilities']) - -/* - * Add bits to $scope for handling callback url help - * - */ - -.factory('CallbackHelpInit', ['$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm', '$stateParams', 'ProcessErrors', 'ParseTypeChange', - 'ParseVariableString', 'Empty', 'InventoryList', 'CredentialList','ProjectList', 'Wait', - function($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors, ParseTypeChange, - ParseVariableString, Empty, InventoryList, CredentialList, ProjectList, Wait) { - return function(params) { - - var scope = params.scope, - defaultUrl = GetBasePath('job_templates'), - // generator = GenerateForm, - form = JobTemplateForm(), - // loadingFinishedCount = 0, - // base = $location.path().replace(/^\//, '').split('/')[0], - master = {}, - id = $stateParams.job_template_id; - // checkSCMStatus, getPlaybooks, callback, - // choicesCount = 0; - - CredentialList = _.cloneDeep(CredentialList); - - // The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the - // popover is activated, a function checks the value of scope.callback_help before constructing the content. - scope.setCallbackHelp = function() { - scope.callback_help = "

With a provisioning callback URL and a host config key a host can contact Tower and request a configuration update using this job " + - "template. The request from the host must be a POST. Here is an example using curl:

\n" + - "
curl --data \"host_config_key=" + scope.example_config_key + "\" " +
-                                  scope.callback_server_path + GetBasePath('job_templates') + scope.example_template_id + "/callback/
\n" + - "

Note the requesting host must be defined in the inventory associated with the job template. If Tower fails to " + - "locate the host, the request will be denied.

" + - "

Successful requests create an entry on the Jobs page, where results and history can be viewed.

"; - }; - - // The md5 helper emits NewMD5Generated whenever a new key is available - if (scope.removeNewMD5Generated) { - scope.removeNewMD5Generated(); - } - scope.removeNewMD5Generated = scope.$on('NewMD5Generated', function() { - scope.configKeyChange(); - }); - - // Fired when user enters a key value - scope.configKeyChange = function() { - scope.example_config_key = scope.host_config_key; - scope.setCallbackHelp(); - }; - - // Set initial values and construct help text - scope.callback_server_path = $location.protocol() + '://' + $location.host() + (($location.port()) ? ':' + $location.port() : ''); - scope.example_config_key = '5a8ec154832b780b9bdef1061764ae5a'; - scope.example_template_id = 'N'; - scope.setCallbackHelp(); - - // this fills the job template form both on copy of the job template - // and on edit - scope.fillJobTemplate = function(){ - // id = id || $rootScope.copy.id; - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl + id); - Rest.get() - .success(function (data) { - scope.job_template_obj = data; - scope.name = data.name; - var fld, i; - for (fld in form.fields) { - if (fld !== 'variables' && fld !== 'survey' && data[fld] !== null && data[fld] !== undefined) { - if (form.fields[fld].type === 'select') { - if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) { - for (i = 0; i < scope[fld + '_options'].length; i++) { - if (data[fld] === scope[fld + '_options'][i].value) { - scope[fld] = scope[fld + '_options'][i]; - } - } - } else { - scope[fld] = data[fld]; - } - } else { - scope[fld] = data[fld]; - if(!Empty(data.summary_fields.survey)) { - scope.survey_exists = true; - } - } - master[fld] = scope[fld]; - } - if (fld === 'variables') { - // Parse extra_vars, converting to YAML. - scope.variables = ParseVariableString(data.extra_vars); - master.variables = scope.variables; - } - if (form.fields[fld].type === 'lookup' && 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] = - scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; - } - if (form.fields[fld].type === 'checkbox_group') { - for(var j=0; j" + i18n._("Submit the request to cancel?") + "
"; - var deleteBody = "
" + i18n._("Are you sure you want to delete the job below?") + "
#" + id + " " + $filter('sanitize')(job.name) + "
"; - Prompt({ - hdr: hdr, - body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody, - action: action, - actionText: (action_label === 'cancel' || job.status === 'new') ? "OK" : "DELETE" - }); - }); - - if (action_label === 'cancel') { - Rest.setUrl(url); - Rest.get() - .success(function(data) { - if (data.can_cancel) { - scope.$emit('CancelJob'); - } - else { - scope.$emit('CancelNotAllowed'); - } - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - ' failed. GET returned: ' + status }); - }); - } - else { - scope.$emit('CancelJob'); - } - - }; - }]) - - .factory('RelaunchInventory', ['Find', 'Wait', 'Rest', 'InventoryUpdate', 'ProcessErrors', 'GetBasePath', - function(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) { - return function(params) { - var scope = params.scope, - id = params.id, - url = GetBasePath('inventory_sources') + id + '/'; - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - InventoryUpdate({ - scope: scope, - url: data.related.update, - group_name: data.summary_fields.group.name, - group_source: data.source, - tree_id: null, - group_id: data.group - }); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + - url + ' GET returned: ' + status }); - }); - }; - }]) - - .factory('RelaunchPlaybook', ['InitiatePlaybookRun', function(InitiatePlaybookRun) { - return function(params) { - var scope = params.scope, - id = params.id, - job_type = params.job_type; - InitiatePlaybookRun({ scope: scope, id: id, relaunch: true, job_type: job_type }); - }; - }]) - - .factory('RelaunchSCM', ['ProjectUpdate', function(ProjectUpdate) { - return function(params) { - var scope = params.scope, - id = params.id; - ProjectUpdate({ scope: scope, project_id: id }); - }; - }]) - - .factory('RelaunchAdhoc', ['AdhocRun', function(AdhocRun) { - return function(params) { - var scope = params.scope, - id = params.id; - AdhocRun({ scope: scope, project_id: id, relaunch: true }); - }; - }]); diff --git a/awx/ui/client/src/helpers/Parse.js b/awx/ui/client/src/helpers/Parse.js deleted file mode 100644 index a2715f6efd..0000000000 --- a/awx/ui/client/src/helpers/Parse.js +++ /dev/null @@ -1,108 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Parse - * @description - * Show the CodeMirror variable editor and allow - * toggle between JSON and YAML - * - */ - -import 'codemirror/lib/codemirror.js'; -import 'codemirror/mode/javascript/javascript.js'; -import 'codemirror/mode/yaml/yaml.js'; -import 'codemirror/addon/lint/lint.js'; -import 'angular-codemirror/lib/yaml-lint.js'; -import 'codemirror/addon/edit/closebrackets.js'; -import 'codemirror/addon/edit/matchbrackets.js'; -import 'codemirror/addon/selection/active-line.js'; - - -export default - angular.module('ParseHelper', ['Utilities', 'AngularCodeMirrorModule']) - .factory('ParseTypeChange', ['Alert', 'AngularCodeMirror', - function (Alert, AngularCodeMirror) { - return function (params) { - - var scope = params.scope, - field_id = params.field_id, - fld = (params.variable) ? params.variable : 'variables', - pfld = (params.parse_variable) ? params.parse_variable : 'parseType', - onReady = params.onReady, - onChange = params.onChange, - readOnly = params.readOnly; - - function removeField(fld) { - //set our model to the last change in CodeMirror and then destroy CodeMirror - scope[fld] = scope[fld + 'codeMirror'].getValue(); - $('#cm-' + fld + '-container > .CodeMirror').empty().remove(); - } - - function createField(onChange, onReady, fld) { - //hide the textarea and show a fresh CodeMirror with the current mode (json or yaml) - - scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly); - scope[fld + 'codeMirror'].addModes(global.$AnsibleConfig.variable_edit_modes); - scope[fld + 'codeMirror'].showTextArea({ - scope: scope, - model: fld, - element: field_id, - lineNumbers: true, - mode: scope[pfld], - onReady: onReady, - onChange: onChange - }); - } - - // Hide the textarea and show a CodeMirror editor - createField(onChange, onReady, fld); - - - // Toggle displayed variable string between JSON and YAML - scope.parseTypeChange = function(model, fld) { - var json_obj; - if (scope[model] === 'json') { - // converting yaml to json - try { - removeField(fld); - json_obj = jsyaml.load(scope[fld]); - if ($.isEmptyObject(json_obj)) { - scope[fld] = "{}"; - } - else { - scope[fld] = JSON.stringify(json_obj, null, " "); - } - createField(onReady, onChange, fld); - } - catch (e) { - Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message); - setTimeout( function() { scope.$apply( function() { scope[model] = 'yaml'; createField(onReady, onChange, fld); }); }, 500); - } - } - else { - // convert json to yaml - try { - removeField(fld); - json_obj = JSON.parse(scope[fld]); - if ($.isEmptyObject(json_obj)) { - scope[fld] = '---'; - } - else { - scope[fld] = jsyaml.safeDump(json_obj); - } - createField(onReady, onChange, fld); - } - catch (e) { - Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message); - setTimeout( function() { scope.$apply( function() { scope[model] = 'json'; createField(onReady, onChange, fld); }); }, 500 ); - } - } - }; - }; - } - ]); diff --git a/awx/ui/client/src/helpers/ProjectPath.js b/awx/ui/client/src/helpers/ProjectPath.js deleted file mode 100644 index 83753be443..0000000000 --- a/awx/ui/client/src/helpers/ProjectPath.js +++ /dev/null @@ -1,91 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:ProjectPath - * @description - * Use GetProjectPath({ scope: , master: }) to - * load scope.project_local_paths (array of options for drop-down) and - * scope.base_dir (readonly field). - * - */ - -export default - angular.module('ProjectPathHelper', ['RestServices', 'Utilities']) - .factory('GetProjectPath', ['Alert', 'Rest', 'GetBasePath', 'ProcessErrors', - function (Alert, Rest, GetBasePath, ProcessErrors) { - return function (params) { - - var scope = params.scope, - master = params.master; - - function arraySort(data) { - //Sort nodes by name - var i, j, names = [], - newData = []; - for (i = 0; i < data.length; i++) { - names.push(data[i].value); - } - names.sort(); - for (j = 0; j < names.length; j++) { - for (i = 0; i < data.length; i++) { - if (data[i].value === names[j]) { - newData.push(data[i]); - } - } - } - return newData; - } - - scope.showMissingPlaybooksAlert = false; - - Rest.setUrl(GetBasePath('config')); - Rest.get() - .success(function (data) { - var opts = [], i; - if (data.project_local_paths) { - for (i = 0; i < data.project_local_paths.length; i++) { - opts.push({ - label: data.project_local_paths[i], - value: data.project_local_paths[i] - }); - } - } - if (scope.local_path) { - // List only includes paths not assigned to projects, so add the - // path assigned to the current project. - opts.push({ - label: scope.local_path, - value: scope.local_path - }); - } - scope.project_local_paths = arraySort(opts); - if (scope.local_path) { - for (i = 0; scope.project_local_paths.length; i++) { - if (scope.project_local_paths[i].value === scope.local_path) { - scope.local_path = scope.project_local_paths[i]; - break; - } - } - } - scope.base_dir = data.project_base_dir; - master.local_path = scope.local_path; - master.base_dir = scope.base_dir; // Keep in master object so that it doesn't get - // wiped out on form reset. - if (opts.length === 0) { - // trigger display of alert block when scm_type == manual - scope.showMissingPlaybooksAlert = true; - } - scope.$emit('pathsReady'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to access API config. GET status: ' + status }); - }); - }; - } - ]); diff --git a/awx/ui/client/src/helpers/Projects.js b/awx/ui/client/src/helpers/Projects.js deleted file mode 100644 index 613dac0c86..0000000000 --- a/awx/ui/client/src/helpers/Projects.js +++ /dev/null @@ -1,84 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Projects - * @description - * Use GetProjectPath({ scope: , master: }) to - * load scope.project_local_paths (array of options for drop-down) and - * scope.base_dir (readonly field). - * - */ - - -export default - angular.module('ProjectsHelper', ['RestServices', 'Utilities', 'ProjectStatusDefinition', 'ProjectFormDefinition']) - - .factory('GetProjectIcon', [ function() { - return function(status) { - var result = ''; - switch (status) { - case 'n/a': - case 'ok': - case 'never updated': - result = 'none'; - break; - case 'pending': - case 'waiting': - case 'new': - result = 'none'; - break; - case 'updating': - case 'running': - result = 'running'; - break; - case 'successful': - result = 'success'; - break; - case 'failed': - case 'missing': - case 'canceled': - result = 'error'; - } - return result; - }; - }]) - - .factory('GetProjectToolTip', ['i18n', function(i18n) { - return function(status) { - var result = ''; - switch (status) { - case 'n/a': - case 'ok': - case 'never updated': - result = i18n._('No SCM updates have run for this project'); - break; - case 'pending': - case 'waiting': - case 'new': - result = i18n._('Queued. Click for details'); - break; - case 'updating': - case 'running': - result = i18n._('Running! Click for details'); - break; - case 'successful': - result = i18n._('Success! Click for details'); - break; - case 'failed': - result = i18n._('Failed. Click for details'); - break; - case 'missing': - result = i18n._('Missing. Click for details'); - break; - case 'canceled': - result = i18n._('Canceled. Click for details'); - break; - } - return result; - }; - }]); diff --git a/awx/ui/client/src/helpers/Schedules.js b/awx/ui/client/src/helpers/Schedules.js deleted file mode 100644 index 90c1589e5b..0000000000 --- a/awx/ui/client/src/helpers/Schedules.js +++ /dev/null @@ -1,506 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Schedules - * @description - * Schedules Helper - * - * Display the scheduler widget in a dialog - * - */ - -import listGenerator from '../shared/list-generator/main'; - -export default - angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', listGenerator.name, 'ModalDialog', - 'GeneratorHelpers']) - - .factory('EditSchedule', ['SchedulerInit', '$rootScope', 'Wait', 'Rest', - 'ProcessErrors', 'GetBasePath', 'SchedulePost', '$state', - function(SchedulerInit, $rootScope, Wait, Rest, ProcessErrors, - GetBasePath, SchedulePost, $state) { - return function(params) { - var scope = params.scope, - id = params.id, - callback = params.callback, - schedule, scheduler, - url = GetBasePath('schedules') + id + '/'; - - delete scope.isFactCleanup; - delete scope.cleanupJob; - - function setGranularity(){ - var a,b, prompt_for_days, - keep_unit, - granularity, - granularity_keep_unit; - - if(scope.cleanupJob){ - scope.schedulerPurgeDays = Number(schedule.extra_data.days); - // scope.scheduler_form.schedulerPurgeDays.$setViewValue( Number(schedule.extra_data.days)); - } - else if(scope.isFactCleanup){ - scope.keep_unit_choices = [{ - "label" : "Days", - "value" : "d" - }, - { - "label": "Weeks", - "value" : "w" - }, - { - "label" : "Years", - "value" : "y" - }]; - scope.granularity_keep_unit_choices = [{ - "label" : "Days", - "value" : "d" - }, - { - "label": "Weeks", - "value" : "w" - }, - { - "label" : "Years", - "value" : "y" - }]; - // the API returns something like 20w or 1y - a = schedule.extra_data.older_than; // "20y" - b = schedule.extra_data.granularity; // "1w" - prompt_for_days = Number(_.initial(a,1).join('')); // 20 - keep_unit = _.last(a); // "y" - granularity = Number(_.initial(b,1).join('')); // 1 - granularity_keep_unit = _.last(b); // "w" - - scope.keep_amount = prompt_for_days; - scope.granularity_keep_amount = granularity; - scope.keep_unit = _.find(scope.keep_unit_choices, function(i){ - return i.value === keep_unit; - }); - scope.granularity_keep_unit =_.find(scope.granularity_keep_unit_choices, function(i){ - return i.value === granularity_keep_unit; - }); - } - } - - if (scope.removeScheduleFound) { - scope.removeScheduleFound(); - } - scope.removeScheduleFound = scope.$on('ScheduleFound', function() { - $('#form-container').empty(); - scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); - scheduler.inject('form-container', false); - scheduler.injectDetail('occurrences', false); - - if (!/DTSTART/.test(schedule.rrule)) { - schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z'); - } - schedule.rrule = schedule.rrule.replace(/ RRULE:/,';'); - schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART='); - scope.$on("htmlDetailReady", function() { - scheduler.setRRule(schedule.rrule); - scheduler.setName(schedule.name); - $rootScope.$broadcast("ScheduleFormCreated", scope); - }); - scope.showRRuleDetail = false; - - scheduler.setRRule(schedule.rrule); - scheduler.setName(schedule.name); - if(scope.isFactCleanup || scope.cleanupJob){ - setGranularity(); - } - }); - - - if (scope.removeScheduleSaved) { - scope.removeScheduleSaved(); - } - scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) { - Wait('stop'); - if (callback) { - scope.$emit(callback, data); - } - $state.go("^"); - }); - scope.saveSchedule = function() { - schedule.extra_data = scope.extraVars; - SchedulePost({ - scope: scope, - url: url, - scheduler: scheduler, - callback: 'ScheduleSaved', - mode: 'edit', - schedule: schedule - }); - }; - - Wait('start'); - - // Get the existing record - Rest.setUrl(url); - Rest.get() - .success(function(data) { - schedule = data; - try { - schedule.extra_data = JSON.parse(schedule.extra_data); - } catch(e) { - // do nothing - } - scope.extraVars = data.extra_data === '' ? '---' : '---\n' + jsyaml.safeDump(data.extra_data); - - if(schedule.extra_data.hasOwnProperty('granularity')){ - scope.isFactCleanup = true; - } - if (schedule.extra_data.hasOwnProperty('days')){ - scope.cleanupJob = true; - } - - scope.schedule_obj = data; - - scope.$emit('ScheduleFound'); - }) - .error(function(data,status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); - }); - }; - }]) - - .factory('AddSchedule', ['$location', '$rootScope', '$stateParams', - 'SchedulerInit', 'Wait', 'GetBasePath', 'Empty', 'SchedulePost', '$state', 'Rest', 'ProcessErrors', - function($location, $rootScope, $stateParams, SchedulerInit, - Wait, GetBasePath, Empty, SchedulePost, $state, Rest, - ProcessErrors) { - return function(params) { - var scope = params.scope, - callback= params.callback, - base = params.base || $location.path().replace(/^\//, '').split('/')[0], - url = params.url || null, - scheduler, - job_type; - - job_type = scope.parentObject.job_type; - if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !url) { - url = GetBasePath(base) + $stateParams.id + '/schedules/'; - } - else if(base === "inventories"){ - if (!params.url){ - url = GetBasePath('groups') + $stateParams.id + '/'; - Rest.setUrl(url); - Rest.get(). - then(function (data) { - url = data.data.related.inventory_source + 'schedules/'; - }).catch(function (response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get inventory group info. GET returned status: ' + - response.status - }); - }); - } - else { - url = params.url; - } - } - else if (base === 'system_job_templates') { - url = GetBasePath(base) + $stateParams.id + '/schedules/'; - if(job_type === "cleanup_facts"){ - scope.isFactCleanup = true; - scope.keep_unit_choices = [{ - "label" : "Days", - "value" : "d" - }, - { - "label": "Weeks", - "value" : "w" - }, - { - "label" : "Years", - "value" : "y" - }]; - scope.granularity_keep_unit_choices = [{ - "label" : "Days", - "value" : "d" - }, - { - "label": "Weeks", - "value" : "w" - }, - { - "label" : "Years", - "value" : "y" - }]; - scope.prompt_for_days_facts_form.keep_amount.$setViewValue(30); - scope.prompt_for_days_facts_form.granularity_keep_amount.$setViewValue(1); - scope.keep_unit = scope.keep_unit_choices[0]; - scope.granularity_keep_unit = scope.granularity_keep_unit_choices[1]; - } - else { - scope.cleanupJob = true; - } - } - - Wait('start'); - $('#form-container').empty(); - scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); - if(scope.schedulerUTCTime) { - // The UTC time is already set - scope.processSchedulerEndDt(); - } - else { - // We need to wait for it to be set by angular-scheduler because the following function depends - // on it - var schedulerUTCTimeWatcher = scope.$watch('schedulerUTCTime', function(newVal) { - if(newVal) { - // Remove the watcher - schedulerUTCTimeWatcher(); - scope.processSchedulerEndDt(); - } - }); - } - scheduler.inject('form-container', false); - scheduler.injectDetail('occurrences', false); - scheduler.clear(); - scope.$on("htmlDetailReady", function() { - $rootScope.$broadcast("ScheduleFormCreated", scope); - }); - scope.showRRuleDetail = false; - - if (scope.removeScheduleSaved) { - scope.removeScheduleSaved(); - } - scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) { - Wait('stop'); - if (callback) { - scope.$emit(callback, data); - } - $state.go("^", null, {reload: true}); - }); - scope.saveSchedule = function() { - SchedulePost({ - scope: scope, - url: url, - scheduler: scheduler, - callback: 'ScheduleSaved', - mode: 'add' - }); - }; - - $('#scheduler-tabs li a').on('shown.bs.tab', function(e) { - if ($(e.target).text() === 'Details') { - if (!scheduler.isValid()) { - $('#scheduler-tabs a:first').tab('show'); - } - } - }); - }; - }]) - - .factory('SchedulePost', ['Rest', 'ProcessErrors', 'RRuleToAPI', 'Wait', - function(Rest, ProcessErrors, RRuleToAPI, Wait) { - return function(params) { - var scope = params.scope, - url = params.url, - scheduler = params.scheduler, - mode = params.mode, - schedule = (params.schedule) ? params.schedule : {}, - callback = params.callback, - newSchedule, rrule, extra_vars; - if (scheduler.isValid()) { - Wait('start'); - newSchedule = scheduler.getValue(); - rrule = scheduler.getRRule(); - schedule.name = newSchedule.name; - schedule.rrule = RRuleToAPI(rrule.toString()); - schedule.description = (/error/.test(rrule.toText())) ? '' : rrule.toText(); - - if (scope.isFactCleanup) { - extra_vars = { - "older_than": scope.scheduler_form.keep_amount.$viewValue + scope.scheduler_form.keep_unit.$viewValue.value, - "granularity": scope.scheduler_form.granularity_keep_amount.$viewValue + scope.scheduler_form.granularity_keep_unit.$viewValue.value - }; - schedule.extra_data = JSON.stringify(extra_vars); - } else if (scope.cleanupJob) { - extra_vars = { - "days" : scope.scheduler_form.schedulerPurgeDays.$viewValue - }; - schedule.extra_data = JSON.stringify(extra_vars); - } - else if(scope.extraVars){ - schedule.extra_data = scope.parseType === 'yaml' ? - (scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars; - } - Rest.setUrl(url); - if (mode === 'add') { - Rest.post(schedule) - .success(function(){ - if (callback) { - scope.$emit(callback); - } - else { - Wait('stop'); - } - }) - .error(function(data, status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'POST to ' + url + ' returned: ' + status }); - }); - } - else { - Rest.put(schedule) - .success(function(){ - if (callback) { - scope.$emit(callback, schedule); - } - else { - Wait('stop'); - } - }) - .error(function(data, status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'POST to ' + url + ' returned: ' + status }); - }); - } - } - else { - return false; - } - }; - }]) - - /** - * Flip a schedule's enable flag - * - * ToggleSchedule({ - * scope: scope, - * id: schedule.id to update - * callback: scope.$emit label to call when update completes - * }); - * - */ - .factory('ToggleSchedule', ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', '$state', - function(Wait, GetBasePath, ProcessErrors, Rest, $state) { - return function(params) { - var scope = params.scope, - id = params.id, - url = GetBasePath('schedules') + id +'/'; - - // Perform the update - if (scope.removeScheduleFound) { - scope.removeScheduleFound(); - } - scope.removeScheduleFound = scope.$on('ScheduleFound', function(e, data) { - data.enabled = (data.enabled) ? false : true; - Rest.put(data) - .success( function() { - Wait('stop'); - $state.go('.', null, {reload: true}); - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status }); - }); - }); - - Wait('start'); - - // Get the schedule - Rest.setUrl(url); - Rest.get() - .success(function(data) { - scope.$emit('ScheduleFound', data); - }) - .error(function(data,status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); - }); - }; - }]) - - /** - * Delete a schedule. Prompts user to confirm delete - * - * DeleteSchedule({ - * scope: $scope containing list of schedules - * id: id of schedule to delete - * callback: $scope.$emit label to call when delete is completed - * }) - * - */ - .factory('DeleteSchedule', ['GetBasePath','Rest', 'Wait', '$state', - 'ProcessErrors', 'Prompt', 'Find', '$location', '$filter', - function(GetBasePath, Rest, Wait, $state, ProcessErrors, Prompt, Find, - $location, $filter) { - return function(params) { - - var scope = params.scope, - id = params.id, - callback = params.callback, - action, schedule, list, url, hdr; - - if (scope.schedules) { - list = scope.schedules; - } - else if (scope.scheduled_jobs) { - list = scope.scheduled_jobs; - } - - url = GetBasePath('schedules') + id + '/'; - schedule = Find({list: list, key: 'id', val: id }); - hdr = 'Delete Schedule'; - - action = function () { - Wait('start'); - Rest.setUrl(url); - Rest.destroy() - .success(function () { - $('#prompt-modal').modal('hide'); - scope.$emit(callback, id); - if (new RegExp('/' + id + '$').test($location.$$url)) { - $location.url($location.url().replace(/[/][0-9]+$/, "")); // go to list view - } - else{ - $state.go('.', null, {reload: true}); - } - }) - .error(function (data, status) { - try { - $('#prompt-modal').modal('hide'); - } - catch(e) { - // ignore - } - ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - ' failed. DELETE returned: ' + status }); - }); - }; - - Prompt({ - hdr: hdr, - body: '
Are you sure you want to delete the schedule below?
' + $filter('sanitize')(schedule.name) + '
', - action: action, - actionText: 'DELETE', - backdrop: false - }); - - }; - }]) - - /** - * Convert rrule string to an API agreeable format - * - */ - .factory('RRuleToAPI', [ function() { - return function(rrule) { - var response; - response = rrule.replace(/(^.*(?=DTSTART))(DTSTART=.*?;)(.*$)/, function(str, p1, p2, p3) { - return p2.replace(/\;/,'').replace(/=/,':') + ' ' + 'RRULE:' + p1 + p3; - }); - return response; - }; - }]); diff --git a/awx/ui/client/src/helpers/Selection.js b/awx/ui/client/src/helpers/Selection.js deleted file mode 100644 index 3fa4fa826b..0000000000 --- a/awx/ui/client/src/helpers/Selection.js +++ /dev/null @@ -1,177 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Selection - * @description - * SelectionHelper - * Used in list controllers where the list might also be used as a selection list. - * - * SelectionInit( { - * scope: , - * list: - * }) - */ - - -export default - angular.module('SelectionHelper', ['Utilities', 'RestServices']) - - .factory('SelectionInit', ['Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'Wait', - function (Rest, Alert, ProcessErrors, ReturnToCaller, Wait) { - return function (params) { - - var scope = params.scope, - list = params.list, - target_url = params.url, - returnToCaller = params.returnToCaller, - selected; - - if (params.selected !== undefined) { - selected = params.selected; - } else { - selected = []; //array of selected row IDs - } - - scope.formModalActionDisabled = true; - scope.disableSelectBtn = true; - - // toggle row selection - scope['toggle_' + list.iterator] = function (id, ischeckbox) { - var i, j, found; - for (i = 0; i < scope[list.name].length; i++) { - if (scope[list.name][i].id === id) { - var control = scope[list.name][i]; - if (ischeckbox && control.checked) { - scope[list.name][i].success_class = 'success'; - // add selected object to the array - found = false; - for (j = 0; j < selected.length; j++) { - if (selected[j].id === id) { - found = true; - break; - } - } - if (!found) { - selected.push(scope[list.name][i]); - } - } else if (ischeckbox) { - scope[list.name][i].success_class = ''; - - // remove selected object from the array - for (j = 0; j < selected.length; j++) { - if (selected[j].id === id) { - selected.splice(j, 1); - break; - } - } - } - } - } - if (selected.length > 0) { - scope.formModalActionDisabled = false; - scope.disableSelectBtn = false; - } else { - scope.formModalActionDisabled = true; - scope.disableSelectBtn = true; - } - }; - - // Add the selections - scope.finishSelection = function () { - Rest.setUrl(target_url); - - var queue = [], j; - - scope.formModalActionDisabled = true; - scope.disableSelectBtn = true; - - Wait('start'); - - function finished() { - selected = []; - if (returnToCaller !== undefined) { - ReturnToCaller(returnToCaller); - } else { - $('#form-modal').modal('hide'); - scope.$emit('modalClosed'); - } - } - - function postIt(data) { - Rest.post(data) - .success(function (data, status) { - queue.push({ result: 'success', data: data, status: status }); - scope.$emit('callFinished'); - }) - .error(function (data, status, headers) { - queue.push({ result: 'error', data: data, status: status, headers: headers }); - scope.$emit('callFinished'); - }); - } - - if (scope.callFinishedRemove) { - scope.callFinishedRemove(); - } - scope.callFinishedRemove = scope.$on('callFinished', function () { - // We call the API for each selected item. We need to hang out until all the api - // calls are finished. - var i, errors=0; - if (queue.length === selected.length) { - Wait('stop'); - for (i = 0; i < queue.length; i++) { - if (queue[i].result === 'error') { - ProcessErrors(scope, queue[i].data, queue[i].status, null, { hdr: 'POST Failure', - msg: 'Failed to add ' + list.iterator + '. POST returned status: ' + queue[i].status }); - errors++; - } - } - if (errors === 0) { - finished(); - } - } - }); - - if (selected.length > 0) { - for (j = 0; j < selected.length; j++) { - postIt(selected[j]); - } - } else { - finished(); - } - }; - - scope.formModalAction = scope.finishSelection; - - // Initialize our data set after a refresh (page change or search) - if (scope.SelectPostRefreshRemove) { - scope.SelectPostRefreshRemove(); - } - scope.SelectPostRefreshRemove = scope.$on('PostRefresh', function () { - var i, j, found; - if (scope[list.name]) { - for (i = 0; i < scope[list.name].length; i++) { - found = false; - for (j = 0; j < selected.length; j++) { - if (selected[j].id === scope[list.name][i].id) { - found = true; - break; - } - } - if (found) { - scope[list.name][i].checked = '1'; - scope[list.name][i].success_class = 'success'; - } else { - scope[list.name][i].checked = '0'; - scope[list.name][i].success_class = ''; - } - } - } - }); - }; - } - ]); diff --git a/awx/ui/client/src/helpers/Users.js b/awx/ui/client/src/helpers/Users.js deleted file mode 100644 index 7f09b486c3..0000000000 --- a/awx/ui/client/src/helpers/Users.js +++ /dev/null @@ -1,46 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Users - * @description - * UserHelper - * Routines shared amongst the user controllers - * - */ - - -export default - angular.module('UserHelper', ['UserFormDefinition']) - .factory('ResetForm', ['UserForm', - function (UserForm) { - return function () { - // Restore form to default conditions. Run before applying LDAP configuration. - // LDAP may manage some or all of these fields in which case the user cannot - // make changes to their values in AWX. - - UserForm.fields.first_name.readonly = false; - UserForm.fields.first_name.editRequired = true; - UserForm.fields.last_name.readonly = false; - UserForm.fields.last_name.editRequired = true; - UserForm.fields.email.readonly = false; - UserForm.fields.email.editRequired = true; - UserForm.fields.organization.awRequiredWhen = { - reqExpression: "orgrequired", - init: true - }; - UserForm.fields.organization.readonly = false; - UserForm.fields.username.awRequiredWhen = { - reqExpression: "not_ldap_user", - init: true - }; - UserForm.fields.username.readonly = false; - UserForm.fields.password.editRequired = false; - UserForm.fields.password.addRrequired = true; - }; - } - ]); diff --git a/awx/ui/client/src/helpers/Variables.js b/awx/ui/client/src/helpers/Variables.js deleted file mode 100644 index 8364c4c15b..0000000000 --- a/awx/ui/client/src/helpers/Variables.js +++ /dev/null @@ -1,180 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Variables - * @description - * VariablesHelper - * - * - */ - -export default -angular.module('VariablesHelper', ['Utilities']) - - /** - variables: string containing YAML or JSON | a JSON object. - - If JSON string, convert to JSON object and run through jsyaml.safeDump() to create a YAML document. If YAML, - will attempt to load via jsyaml.safeLoad() and return a YAML document using jsyaml.safeDump(). In all cases - a YAML document is returned. - **/ - .factory('ParseVariableString', ['$log', 'ProcessErrors', 'SortVariables', function ($log, ProcessErrors, SortVariables) { - return function (variables) { - var result = "---", json_obj; - if (typeof variables === 'string') { - if (variables === "{}" || variables === "null" || variables === "" || variables === "\"\"") { - // String is empty, return --- - } else { - try { - json_obj = JSON.parse(variables); - json_obj = SortVariables(json_obj); - result = jsyaml.safeDump(json_obj); - - } - catch (e) { - $log.debug('Attempt to parse extra_vars as JSON failed. Check that the variables parse as yaml. Set the raw string as the result.'); - try { - // do safeLoad, which well error if not valid yaml - json_obj = jsyaml.safeLoad(variables); - // but just send the variables - result = variables; - } - catch(e2) { - ProcessErrors(null, variables, e2.message, null, { hdr: 'Error!', - msg: 'Attempts to parse variables as JSON and YAML failed. Last attempt returned: ' + e2.message }); - } - } - } - } - else { - if ($.isEmptyObject(variables) || variables === null) { - // Empty object, return --- - } - else { - // convert object to yaml - try { - json_obj = SortVariables(variables); - result = jsyaml.safeDump(json_obj); - // result = variables; - } - catch(e3) { - ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!', - msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message }); - } - } - } - return result; - }; - }]) - - /** - parseType: 'json' | 'yaml' - variables: string containing JSON or YAML - stringify: optional, boolean - - Parse the given string according to the parseType to a JSON object. If stringify true, - stringify the object and return the string. Otherwise, return the JSON object. - - **/ - .factory('ToJSON', ['$log', 'ProcessErrors', function($log, ProcessErrors) { - return function(parseType, variables, stringify, reviver) { - var json_data, - result, - tmp; - // bracketVar, - // key, - // lines, i, newVars = []; - if (parseType === 'json') { - try { - // perform a check to see if the user cleared the field completly - if(variables.trim() === "" || variables.trim() === "{" || variables.trim() === "}" ){ - variables = "{}"; - } - //parse a JSON string - if (reviver) { - json_data = JSON.parse(variables, reviver); - } - else { - json_data = JSON.parse(variables); - } - } - catch(e) { - json_data = {}; - $log.error('Failed to parse JSON string. Parser returned: ' + e.message); - ProcessErrors(null, variables, e.message, null, { hdr: 'Error!', - msg: 'Failed to parse JSON string. Parser returned: ' + e.message }); - throw 'Parse error. Failed to parse variables.'; - } - } else { - try { - if(variables.trim() === "" || variables.trim() === "-" || variables.trim() === "--"){ - variables = '---'; - } - json_data = jsyaml.safeLoad(variables); - if(json_data!==null){ - // unparsing just to make sure no weird characters are included. - tmp = jsyaml.dump(json_data); - if(tmp.indexOf('[object Object]')!==-1){ - throw "Failed to parse YAML string. Parser returned' + key + ' : ' +value + '.' "; - } - } - } - catch(e) { - json_data = undefined; // {}; - // $log.error('Failed to parse YAML string. Parser returned undefined'); - ProcessErrors(null, variables, e.message, null, { hdr: 'Error!', - msg: 'Failed to parse YAML string. Parser returned undefined'}); - } - } - // Make sure our JSON is actually an object - if (typeof json_data !== 'object') { - ProcessErrors(null, variables, null, null, { hdr: 'Error!', - msg: 'Failed to parse variables. Attempted to parse ' + parseType + '. Parser did not return an object.' }); - // setTimeout( function() { - throw 'Parse error. Failed to parse variables.'; - // }, 1000); - } - result = json_data; - if (stringify) { - if(json_data === undefined){ - result = undefined; - } - else if ($.isEmptyObject(json_data)) { - result = ""; - } else { - // utilize the parsing to get here - // but send the raw variable string - result = variables; - } - } - return result; - }; - }]) - - .factory('SortVariables', [ function() { - return function(variableObj) { - var newObj; - function sortIt(objToSort) { - var i, - keys = Object.keys(objToSort), - newObj = {}; - keys = keys.sort(); - for (i=0; i < keys.length; i++) { - if (typeof objToSort[keys[i]] === 'object' && objToSort[keys[i]] !== null && !Array.isArray(objToSort[keys[i]])) { - newObj[keys[i]] = sortIt(objToSort[keys[i]]); - } - else { - newObj[keys[i]] = objToSort[keys[i]]; - } - } - return newObj; - } - newObj = sortIt(variableObj); - return newObj; - }; - }]); diff --git a/awx/ui/client/src/helpers/api-defaults.js b/awx/ui/client/src/helpers/api-defaults.js deleted file mode 100644 index bf9308b0da..0000000000 --- a/awx/ui/client/src/helpers/api-defaults.js +++ /dev/null @@ -1,94 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:api-defaults - * @description this could use more discussion -*/ - -export default - angular.module('APIDefaults', ['RestServices', 'Utilities']) - .factory('GetAPIDefaults', ['Alert', 'Rest', '$rootScope', - function (Alert, Rest, $rootScope) { - return function (key) { - - //Reload a related collection on pagination or search change - - var result = {}, cnt = 0, url; - - function lookup(key) { - var id, result = {}; - for (id in $rootScope.apiDefaults) { - if (id === key || id.iterator === key) { - result[id] = $rootScope.apiDefaults[id]; - break; - } - } - return result; - } - - function wait() { - if ($.isEmptyObject(result) && cnt < 5) { - cnt++; - setTimeout(1000, wait()); - } else if (result.status === 'success') { - return lookup(key); - } - } - - if ($rootScope.apiDefaults === null || $rootScope.apiDefaults === undefined) { - url = '/api/v1/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - var id, defaults = data; - for (id in defaults) { - switch (id) { - case 'organizations': - defaults[id].iterator = 'organization'; - break; - case 'jobs': - defaults[id].iterator = 'job'; - break; - case 'users': - defaults[id].iterator = 'user'; - break; - case 'teams': - defaults[id].iterator = 'team'; - break; - case 'hosts': - defaults[id].iterator = 'host'; - break; - case 'groups': - defaults[id].iterator = 'group'; - break; - case 'projects': - defaults[id].iterator = 'project'; - break; - case 'inventories': - defaults[id].iterator = 'inventory'; - break; - } - } - $rootScope.apiDefaults = defaults; - result = { - status: 'success' - }; - }) - .error(function (data, status) { - result = { - status: 'error', - msg: 'Call to ' + url + ' failed. GET returned status: ' + status - }; - }); - return wait(); - } else { - return lookup(key); - } - }; - } - ]); diff --git a/awx/ui/client/src/helpers/inventory.js b/awx/ui/client/src/helpers/inventory.js deleted file mode 100644 index 1310899fa0..0000000000 --- a/awx/ui/client/src/helpers/inventory.js +++ /dev/null @@ -1,84 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Inventory - * @description InventoryHelper - * Routines for building the tree. Everything related to the tree is here except - * for the menu piece. The routine for building the menu is in InventoriesEdit controller - * (controllers/Inventories.js) -*/ - -import listGenerator from '../shared/list-generator/main'; - -export default - angular.module('InventoryHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name, - 'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'VariablesHelper', - ]) - - .factory('SaveInventory', ['InventoryForm', 'Rest', 'Alert', 'ProcessErrors', 'OrganizationList', - 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', - function (InventoryForm, Rest, Alert, ProcessErrors, OrganizationList, GetBasePath, ParseTypeChange, Wait, - ToJSON) { - return function (params) { - - // Save inventory property modifications - - var scope = params.scope, - form = InventoryForm, - defaultUrl = GetBasePath('inventory'), - fld, json_data, data; - - Wait('start'); - - // Make sure we have valid variable data - json_data = ToJSON(scope.parseType, scope.variables); - - data = {}; - for (fld in form.fields) { - if (fld !== 'variables') { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = scope[fld]; - } else { - data[fld] = scope[fld]; - } - } - } - - if (scope.removeUpdateInventoryVariables) { - scope.removeUpdateInventoryVariables(); - } - scope.removeUpdateInventoryVariables = scope.$on('UpdateInventoryVariables', function(e, data) { - Rest.setUrl(data.related.variable_data); - Rest.put(json_data) - .success(function () { - Wait('stop'); - scope.$emit('InventorySaved'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to update inventory varaibles. PUT returned status: ' + status - }); - }); - }); - - Rest.setUrl(defaultUrl + scope.inventory_id + '/'); - Rest.put(data) - .success(function (data) { - if (scope.variables) { - scope.$emit('UpdateInventoryVariables', data); - } else { - scope.$emit('InventorySaved'); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to update inventory. PUT returned status: ' + status }); - }); - }; - } - ]); diff --git a/awx/ui/client/src/helpers/md5.js b/awx/ui/client/src/helpers/md5.js deleted file mode 100644 index 26590be8fc..0000000000 --- a/awx/ui/client/src/helpers/md5.js +++ /dev/null @@ -1,46 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:md5 - * @description - * Run md5Setup({ scope: , master:, check_field:, default_val: }) - * to initialize md5 fields (checkbox and text field). - * discussion - */ - - -export default - angular.module('md5Helper', ['RestServices', 'Utilities', require('angular-md5')]) - .factory('md5Setup', ['md5', function (md5) { - return function (params) { - - var scope = params.scope, - master = params.master, - check_field = params.check_field, - default_val = params.default_val; - - scope[check_field] = default_val; - master[check_field] = default_val; - - scope.genMD5 = function (fld) { - var now = new Date(); - scope[fld] = md5.createHash('AnsibleWorks' + now.getTime()); - scope.$emit('NewMD5Generated'); - }; - - scope.toggleCallback = function (fld) { - if (scope.allow_callbacks === false) { - scope[fld] = ''; - } - }; - - scope.selectAll = function (fld) { - $('input[name="' + fld + '"]').focus().select(); - }; - }; - }]); diff --git a/awx/ui/client/src/helpers/teams.js b/awx/ui/client/src/helpers/teams.js deleted file mode 100644 index b89d2a1283..0000000000 --- a/awx/ui/client/src/helpers/teams.js +++ /dev/null @@ -1,76 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name helpers.function:teams - * @description - * TeamHelper - * Routines shared amongst the team controllers - */ - -import listGenerator from '../shared/list-generator/main'; - -export default - angular.module('TeamHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name - ]) - .factory('SetTeamListeners', ['Alert', 'Rest', - function (Alert, Rest) { - return function (params) { - - var scope = params.scope, - set = params.set; - - // Listeners to perform lookups after main inventory list loads - - scope.$on('TeamResultFound', function (e, results, lookup_results) { - var i, j, key, property; - if (lookup_results.length === results.length) { - key = 'organization'; - property = 'organization_name'; - for (i = 0; i < results.length; i++) { - for (j = 0; j < lookup_results.length; j++) { - if (results[i][key] === lookup_results[j].id) { - results[i][property] = lookup_results[j].value; - } - } - } - - // @issue: OLD SEARCH - // scope[iterator + 'SearchSpin'] = false; - - scope[set] = results; - } - }); - - scope.$on('TeamRefreshFinished', function (e, results) { - // Loop through the result set (sent to us by the search helper) and - // lookup the id and name of each organization. After each lookup - // completes, call resultFound. - - var i, lookup_results = [], url; - - function getOrganization(url) { - Rest.setUrl(url); - Rest.get() - .success(function (data) { - lookup_results.push({ id: data.id, value: data.name }); - scope.$emit('TeamResultFound', results, lookup_results); - }) - .error(function () { - lookup_results.push({ id: 'error' }); - scope.$emit('TeamResultFound', results, lookup_results); - }); - } - - for (i = 0; i < results.length; i++) { - url = '/api/v1/organizations/' + results[i].organization + '/'; - getOrganization(url); - } - }); - }; - } - ]); diff --git a/awx/ui/client/src/home/dashboard/lists/job-templates/main.js b/awx/ui/client/src/home/dashboard/lists/job-templates/main.js index 1a47bb600b..9825630182 100644 --- a/awx/ui/client/src/home/dashboard/lists/job-templates/main.js +++ b/awx/ui/client/src/home/dashboard/lists/job-templates/main.js @@ -1,6 +1,5 @@ import JobTemplatesListDirective from './job-templates-list.directive'; import systemStatus from '../../../../smart-status/main'; -import jobSubmissionHelper from '../../../../helpers/JobSubmission'; -export default angular.module('JobTemplatesList', [systemStatus.name, jobSubmissionHelper.name]) +export default angular.module('JobTemplatesList', [systemStatus.name]) .directive('jobTemplatesList', JobTemplatesListDirective); diff --git a/awx/ui/client/src/inventories/manage/groups/factories/get-hosts-status-msg.factory.js b/awx/ui/client/src/inventories/manage/groups/factories/get-hosts-status-msg.factory.js new file mode 100644 index 0000000000..19a846c414 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/groups/factories/get-hosts-status-msg.factory.js @@ -0,0 +1,33 @@ +export default + function GetHostsStatusMsg() { + return function(params) { + var active_failures = params.active_failures, + total_hosts = params.total_hosts, + tip, failures, html_class; + + // Return values for use on host status indicator + + if (active_failures > 0) { + tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + active_failures + ' with failed jobs.'; + html_class = 'error'; + failures = true; + } else { + failures = false; + if (total_hosts === 0) { + // no hosts + tip = "Contains 0 hosts."; + html_class = 'none'; + } else { + // many hosts with 0 failures + tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. No job failures'; + html_class = 'success'; + } + } + + return { + tooltip: tip, + failures: failures, + 'class': html_class + }; + }; + } diff --git a/awx/ui/client/src/inventories/manage/groups/factories/get-source-type-options.factory.js b/awx/ui/client/src/inventories/manage/groups/factories/get-source-type-options.factory.js new file mode 100644 index 0000000000..befef8a499 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/groups/factories/get-source-type-options.factory.js @@ -0,0 +1,37 @@ +export default + function GetSourceTypeOptions(Rest, ProcessErrors, GetBasePath) { + return function(params) { + var scope = params.scope, + variable = params.variable; + + if (scope[variable] === undefined) { + scope[variable] = []; + Rest.setUrl(GetBasePath('inventory_sources')); + Rest.options() + .success(function (data) { + var i, choices = data.actions.GET.source.choices; + for (i = 0; i < choices.length; i++) { + if (choices[i][0] !== 'file') { + scope[variable].push({ + label: choices[i][1], + value: choices[i][0] + }); + } + } + scope.cloudCredentialRequired = false; + scope.$emit('sourceTypeOptionsReady'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve options for inventory_sources.source. OPTIONS status: ' + status + }); + }); + } + }; + } + +GetSourceTypeOptions.$inject = + [ 'Rest', + 'ProcessErrors', + 'GetBasePath' + ]; diff --git a/awx/ui/client/src/inventories/manage/groups/factories/get-sync-status-msg.factory.js b/awx/ui/client/src/inventories/manage/groups/factories/get-sync-status-msg.factory.js new file mode 100644 index 0000000000..2541abcc27 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/groups/factories/get-sync-status-msg.factory.js @@ -0,0 +1,77 @@ +export default + function GetSyncStatusMsg(Empty) { + return function(params) { + var status = params.status, + source = params.source, + has_inventory_sources = params.has_inventory_sources, + launch_class = '', + launch_tip = 'Start sync process', + schedule_tip = 'Schedule future inventory syncs', + stat, stat_class, status_tip; + + stat = status; + stat_class = stat; + + switch (status) { + case 'never updated': + stat = 'never'; + stat_class = 'na'; + status_tip = 'Sync not performed. Click to start it now.'; + break; + case 'none': + case 'ok': + case '': + launch_class = 'btn-disabled'; + stat = 'n/a'; + stat_class = 'na'; + status_tip = 'Cloud source not configured. Click to update.'; + launch_tip = 'Cloud source not configured.'; + break; + case 'canceled': + status_tip = 'Sync canceled. Click to view log.'; + break; + case 'failed': + status_tip = 'Sync failed. Click to view log.'; + break; + case 'successful': + status_tip = 'Sync completed. Click to view log.'; + break; + case 'pending': + status_tip = 'Sync pending.'; + launch_class = "btn-disabled"; + launch_tip = "Sync pending"; + break; + case 'updating': + case 'running': + launch_class = "btn-disabled"; + launch_tip = "Sync running"; + status_tip = "Sync running. Click to view log."; + break; + } + + if (has_inventory_sources && Empty(source)) { + // parent has a source, therefore this group should not have a source + launch_class = "btn-disabled"; + status_tip = 'Managed by an external cloud source.'; + launch_tip = 'Can only be updated by running a sync on the parent group.'; + } + + if (has_inventory_sources === false && Empty(source)) { + launch_class = 'btn-disabled'; + status_tip = 'Cloud source not configured. Click to update.'; + launch_tip = 'Cloud source not configured.'; + } + + return { + "class": stat_class, + "tooltip": status_tip, + "status": stat, + "launch_class": launch_class, + "launch_tip": launch_tip, + "schedule_tip": schedule_tip + }; + }; + } + +GetSyncStatusMsg.$inject = + [ 'Empty' ]; diff --git a/awx/ui/client/src/inventories/manage/groups/factories/groups-cancel-update.factory.js b/awx/ui/client/src/inventories/manage/groups/factories/groups-cancel-update.factory.js new file mode 100644 index 0000000000..1447d0aa1c --- /dev/null +++ b/awx/ui/client/src/inventories/manage/groups/factories/groups-cancel-update.factory.js @@ -0,0 +1,81 @@ +export default + function GroupsCancelUpdate(Empty, Rest, ProcessErrors, Alert, Wait, Find) { + return function(params) { + var scope = params.scope, + id = params.id, + group = params.group; + + if (scope.removeCancelUpdate) { + scope.removeCancelUpdate(); + } + scope.removeCancelUpdate = scope.$on('CancelUpdate', function (e, url) { + // Cancel the update process + Rest.setUrl(url); + Rest.post() + .success(function () { + Wait('stop'); + //Alert('Inventory Sync Cancelled', 'Request to cancel the sync process was submitted to the task manger. ' + + // 'Click the button to monitor the status.', 'alert-info'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. POST status: ' + status + }); + }); + }); + + if (scope.removeCheckCancel) { + scope.removeCheckCancel(); + } + scope.removeCheckCancel = scope.$on('CheckCancel', function (e, last_update, current_update) { + // Check that we have access to cancelling an update + var url = (current_update) ? current_update : last_update; + url += 'cancel/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + if (data.can_cancel) { + scope.$emit('CancelUpdate', url); + //} else { + // Wait('stop'); + // Alert('Cancel Inventory Sync', 'The sync process completed. Click the button to view ' + + // 'the latest status.', 'alert-info'); + } + else { + Wait('stop'); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET status: ' + status + }); + }); + }); + + // Cancel the update process + if (Empty(group)) { + group = Find({ list: scope.groups, key: 'id', val: id }); + scope.selected_group_id = group.id; + } + + if (group && (group.status === 'running' || group.status === 'pending')) { + // We found the group, and there is a running update + Wait('start'); + Rest.setUrl(group.related.inventory_source); + Rest.get() + .success(function (data) { + scope.$emit('CheckCancel', data.related.last_update, data.related.current_update); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + group.related.inventory_source + ' failed. GET status: ' + status + }); + }); + } + }; + } + +GroupsCancelUpdate.$inject = + [ 'Empty', 'Rest', 'ProcessErrors', + 'Alert', 'Wait', 'Find' + ]; diff --git a/awx/ui/client/src/inventories/manage/groups/factories/view-update-status.factory.js b/awx/ui/client/src/inventories/manage/groups/factories/view-update-status.factory.js new file mode 100644 index 0000000000..da186e77aa --- /dev/null +++ b/awx/ui/client/src/inventories/manage/groups/factories/view-update-status.factory.js @@ -0,0 +1,46 @@ +export default + function ViewUpdateStatus($state, Rest, ProcessErrors, GetBasePath, Alert, Wait, Empty, Find) { + return function(params) { + var scope = params.scope, + group_id = params.group_id, + group = Find({ list: scope.groups, key: 'id', val: group_id }); + + if (scope.removeSourceReady) { + scope.removeSourceReady(); + } + scope.removeSourceReady = scope.$on('SourceReady', function(e, source) { + + // Get the ID from the correct summary field + var update_id = (source.summary_fields.current_update) ? source.summary_fields.current_update.id : source.summary_fields.last_update.id; + + $state.go('inventorySyncStdout', {id: update_id}); + + }); + + if (group) { + if (Empty(group.source)) { + // do nothing + } else if (Empty(group.status) || group.status === "never updated") { + Alert('No Status Available', '
An inventory sync has not been performed for the selected group. Start the process by ' + + 'clicking the button.
', 'alert-info', null, null, null, null, true); + } else { + Wait('start'); + Rest.setUrl(group.related.inventory_source); + Rest.get() + .success(function (data) { + scope.$emit('SourceReady', data); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source + + ' GET returned status: ' + status }); + }); + } + } + }; + } + +ViewUpdateStatus.$inject = + [ '$state', 'Rest', 'ProcessErrors', + 'GetBasePath', 'Alert', 'Wait', 'Empty', 'Find' + ]; diff --git a/awx/ui/client/src/inventories/manage/groups/main.js b/awx/ui/client/src/inventories/manage/groups/main.js index 35e7b80c59..e1c2c16ddb 100644 --- a/awx/ui/client/src/inventories/manage/groups/main.js +++ b/awx/ui/client/src/inventories/manage/groups/main.js @@ -6,8 +6,18 @@ import GroupAddController from './groups-add.controller'; import GroupEditController from './groups-edit.controller'; +import GetHostsStatusMsg from './factories/get-hosts-status-msg.factory'; +import GetSourceTypeOptions from './factories/get-source-type-options.factory'; +import GetSyncStatusMsg from './factories/get-sync-status-msg.factory'; +import GroupsCancelUpdate from './factories/groups-cancel-update.factory'; +import ViewUpdateStatus from './factories/view-update-status.factory'; export default angular.module('manageGroups', []) + .factory('GetHostsStatusMsg', GetHostsStatusMsg) + .factory('GetSourceTypeOptions', GetSourceTypeOptions) + .factory('GetSyncStatusMsg', GetSyncStatusMsg) + .factory('GroupsCancelUpdate', GroupsCancelUpdate) + .factory('ViewUpdateStatus', ViewUpdateStatus) .controller('GroupAddController', GroupAddController) .controller('GroupEditController', GroupEditController); diff --git a/awx/ui/client/src/inventories/manage/hosts/factories/set-enabled-msg.factory.js b/awx/ui/client/src/inventories/manage/hosts/factories/set-enabled-msg.factory.js new file mode 100644 index 0000000000..7c60174611 --- /dev/null +++ b/awx/ui/client/src/inventories/manage/hosts/factories/set-enabled-msg.factory.js @@ -0,0 +1,13 @@ +export default + function SetEnabledMsg() { + return function(host) { + if (host.has_inventory_sources) { + // Inventory sync managed, so not clickable + host.enabledToolTip = (host.enabled) ? 'Host is available' : 'Host is not available'; + } + else { + // Clickable + host.enabledToolTip = (host.enabled) ? 'Host is available. Click to toggle.' : 'Host is not available. Click to toggle.'; + } + }; + } diff --git a/awx/ui/client/src/inventories/manage/hosts/factories/set-status.factory.js b/awx/ui/client/src/inventories/manage/hosts/factories/set-status.factory.js new file mode 100644 index 0000000000..c03468ee1f --- /dev/null +++ b/awx/ui/client/src/inventories/manage/hosts/factories/set-status.factory.js @@ -0,0 +1,102 @@ +export default + function SetStatus($filter, SetEnabledMsg, Empty) { + return function(params) { + var scope = params.scope, + host = params.host, + i, html, title; + + function ellipsis(a) { + if (a.length > 25) { + return a.substr(0,25) + '...'; + } + return a; + } + + function noRecentJobs() { + title = 'No job data'; + html = "

No recent job data available for this host.

\n"; + } + + function setMsg(host) { + var j, job, jobs; + + if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) { + if (host.has_active_failures === true) { + host.badgeToolTip = 'Most recent job failed. Click to view jobs.'; + host.active_failures = 'error'; + } + else { + host.badgeToolTip = "Most recent job successful. Click to view jobs."; + host.active_failures = 'successful'; + } + if (host.summary_fields.recent_jobs.length > 0) { + // build html table of job status info + jobs = host.summary_fields.recent_jobs.sort( + function(a,b) { + // reverse numerical order + return -1 * (a - b); + }); + title = "Recent Jobs"; + html = "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + for (j=0; j < jobs.length; j++) { + job = jobs[j]; + html += "\n"; + + // SmartStatus-tooltips are named --success whereas icon-job uses successful + var iconStatus = (job.status === 'successful') ? 'success' : 'failed'; + + html += "\n"; + + html += "\n"; + + html += "\n"; + + html += "\n"; + } + html += "\n"; + html += "
StatusFinishedName
" + ($filter('longDate')(job.finished)).replace(/ /,'
') + "
" + ellipsis(job.name) + "
\n"; + } + else { + noRecentJobs(); + } + } + else if (host.has_active_failures === false && host.last_job === null) { + host.badgeToolTip = "No job data available."; + host.active_failures = 'none'; + noRecentJobs(); + } + host.job_status_html = html; + host.job_status_title = title; + } + + if (!Empty(host)) { + // update single host + setMsg(host); + SetEnabledMsg(host); + } + else { + // update all hosts + for (i=0; i < scope.hosts.length; i++) { + setMsg(scope.hosts[i]); + SetEnabledMsg(scope.hosts[i]); + } + } + }; + } + +SetStatus.$inject = + [ '$filter', + 'SetEnabledMsg', + 'Empty' + ]; diff --git a/awx/ui/client/src/inventories/manage/hosts/main.js b/awx/ui/client/src/inventories/manage/hosts/main.js index 6dd1f334a7..1b22bd3d35 100644 --- a/awx/ui/client/src/inventories/manage/hosts/main.js +++ b/awx/ui/client/src/inventories/manage/hosts/main.js @@ -6,8 +6,12 @@ import HostsAddController from './hosts-add.controller'; import HostsEditController from './hosts-edit.controller'; +import SetStatus from './factories/set-status.factory'; +import SetEnabledMsg from './factories/set-enabled-msg.factory'; export default angular.module('manageHosts', []) + .factory('SetStatus', SetStatus) + .factory('SetEnabledMsg', SetEnabledMsg) .controller('HostsAddController', HostsAddController) .controller('HostEditController', HostsEditController); diff --git a/awx/ui/client/src/job-submission/job-submission-factories/adhoc-run.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/adhoc-run.factory.js new file mode 100644 index 0000000000..bcc688936f --- /dev/null +++ b/awx/ui/client/src/job-submission/job-submission-factories/adhoc-run.factory.js @@ -0,0 +1,162 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name helpers.function:Adhoc + * @description These routines are shared by adhoc command related controllers. + * The content here is very similar to the JobSubmission helper, and in fact, + * certain services are pulled from that helper. This leads to an important + * point: if you need to create functionality that is shared between the command + * and playbook run process, put that code in the JobSubmission helper and make + * it into a reusable step (by specifying a callback parameter in the factory). + * For a good example of this, please see how the AdhocLaunch factory in this + * file utilizes the CheckPasswords factory from the JobSubmission helper. + * + * #AdhocRelaunch Step 1: preparing the GET to ad_hoc_commands/n/relaunch + * The adhoc relaunch process is called from the JobSubmission helper. It is a + * separate process from the initial adhoc run becuase of the way the API + * endpoints work. For AdhocRelaunch, we have access to the original run and + * we can pull the related relaunch URL by knowing the original Adhoc runs ID. + * + * #AdhocRelaunch Step 2: If we got passwords back, add them + * The relaunch URL gives us back the passwords we need to prompt for (if any). + * We'll go to step 3 if there are passwords, and step 4 if not. + * + * #AdhocRelaunch Step 3: PromptForPasswords and the CreateLaunchDialog + * + * #AdhocRelaunch Step 5: StartAdhocRun + * + * #AdhocRelaunch Step 6: LaunchJob and navigate to the standard out page. + + * **If you are + * TODO: once the API endpoint is figured out for running an adhoc command + * from the form is figured out, the rest work should probably be excised from + * the controller and moved into here. See the todo statements in the + * controller for more information about this. + */ + + export default + function AdhocRun($location, $stateParams, LaunchJob, PromptForPasswords, + Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog, $state) { + return function(params) { + var id = params.project_id, + scope = params.scope.$new(), + new_job_id, + html, + url; + + // this is used to cancel a running adhoc command from + // the jobs page + if (scope.removeCancelJob) { + scope.removeCancelJob(); + } + scope.removeCancelJob = scope.$on('CancelJob', function() { + // Delete the job + Wait('start'); + Rest.setUrl(GetBasePath('ad_hoc_commands') + new_job_id + '/'); + Rest.destroy() + .success(function() { + Wait('stop'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, + null, { hdr: 'Error!', + msg: 'Call to ' + url + + ' failed. DELETE returned status: ' + + status }); + }); + }); + + if (scope.removeStartAdhocRun) { + scope.removeStartAdhocRun(); + } + + scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() { + var password, + postData={}; + for (password in scope.passwords) { + postData[scope.passwords[password]] = scope[ + scope.passwords[password] + ]; + } + // Re-launch the adhoc job + Rest.setUrl(url); + Rest.post(postData) + .success(function (data) { + Wait('stop'); + if($location.path().replace(/^\//, '').split('/')[0] !== 'jobs') { + $state.go('adHocJobStdout', {id: data.id}); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, { + hdr: 'Error!', + msg: 'Failed to launch adhoc command. POST ' + + 'returned status: ' + status }); + }); + }); + + // start routine only if passwords need to be prompted + if (scope.removeCreateLaunchDialog) { + scope.removeCreateLaunchDialog(); + } + scope.removeCreateLaunchDialog = scope.$on('CreateLaunchDialog', + function(e, html, url) { + CreateLaunchDialog({ + scope: scope, + html: html, + url: url, + callback: 'StartAdhocRun' + }); + }); + + if (scope.removePromptForPasswords) { + scope.removePromptForPasswords(); + } + scope.removePromptForPasswords = scope.$on('PromptForPasswords', + function(e, passwords_needed_to_start,html, url) { + PromptForPasswords({ + scope: scope, + passwords: passwords_needed_to_start, + callback: 'CreateLaunchDialog', + html: html, + url: url + }); + }); // end password prompting routine + + // start the adhoc relaunch routine + Wait('start'); + url = GetBasePath('ad_hoc_commands') + id + '/relaunch/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + new_job_id = data.id; + + scope.passwords_needed_to_start = data.passwords_needed_to_start; + if (!Empty(data.passwords_needed_to_start) && + data.passwords_needed_to_start.length > 0) { + // go through the password prompt routine before + // starting the adhoc run + scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url); + } + else { + // no prompting of passwords needed + scope.$emit('StartAdhocRun'); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get job template details. GET returned status: ' + status }); + }); + }; + } + + AdhocRun.$inject = + [ '$location','$stateParams', 'LaunchJob', + 'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors', + 'Wait', 'Empty', 'CreateLaunchDialog', '$state' + ]; diff --git a/awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js new file mode 100644 index 0000000000..4f6089f7e2 --- /dev/null +++ b/awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js @@ -0,0 +1,43 @@ +export default + function CheckPasswords(Rest, GetBasePath, ProcessErrors, Empty) { + return function(params) { + var scope = params.scope, + callback = params.callback, + credential = params.credential; + + var passwords = []; + if (!Empty(credential)) { + Rest.setUrl(GetBasePath('credentials')+credential); + Rest.get() + .success(function (data) { + if(data.kind === "ssh"){ + if(data.password === "ASK" ){ + passwords.push("ssh_password"); + } + if(data.ssh_key_unlock === "ASK"){ + passwords.push("ssh_key_unlock"); + } + if(data.become_password === "ASK"){ + passwords.push("become_password"); + } + if(data.vault_password === "ASK"){ + passwords.push("vault_password"); + } + } + scope.$emit(callback, passwords); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get job template details. GET returned status: ' + status }); + }); + } + + }; + } + +CheckPasswords.$inject = + [ 'Rest', + 'GetBasePath', + 'ProcessErrors', + 'Empty' + ]; diff --git a/awx/ui/client/src/job-submission/job-submission-factories/create-launch-dialog.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/create-launch-dialog.factory.js new file mode 100644 index 0000000000..9620807621 --- /dev/null +++ b/awx/ui/client/src/job-submission/job-submission-factories/create-launch-dialog.factory.js @@ -0,0 +1,75 @@ +export default + function CreateLaunchDialog($compile, CreateDialog, Wait, ParseTypeChange) { + return function(params) { + var buttons, + scope = params.scope, + html = params.html, + // job_launch_data = {}, + callback = params.callback || 'PlaybookLaunchFinished', + // url = params.url, + e; + + // html+='
job_launch_form.$valid = {{job_launch_form.$valid}}
'; + html+=''; + $('#password-modal').empty().html(html); + $('#password-modal').find('#job_extra_vars').before(scope.helpContainer); + e = angular.element(document.getElementById('password-modal')); + $compile(e)(scope); + + if(scope.prompt_for_vars===true){ + ParseTypeChange({ scope: scope, field_id: 'job_extra_vars' , variable: "extra_vars"}); + } + + buttons = [{ + label: "Cancel", + onClick: function() { + $('#password-modal').dialog('close'); + // scope.$emit('CancelJob'); + // scope.$destroy(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "password-cancel-button" + },{ + label: "Launch", + onClick: function() { + scope.$emit(callback); + $('#password-modal').dialog('close'); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "password-accept-button" + }]; + + CreateDialog({ + id: 'password-modal', + scope: scope, + buttons: buttons, + width: 620, + height: 700, //(scope.passwords.length > 1) ? 700 : 500, + minWidth: 500, + title: 'Launch Configuration', + callback: 'DialogReady', + onOpen: function(){ + Wait('stop'); + } + }); + + if (scope.removeDialogReady) { + scope.removeDialogReady(); + } + scope.removeDialogReady = scope.$on('DialogReady', function() { + $('#password-modal').dialog('open'); + $('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' ); + e = angular.element(document.getElementById('password-accept-button')); + $compile(e)(scope); + }); + }; + } + +CreateLaunchDialog.$inject = + [ '$compile', + 'CreateDialog', + 'Wait', + 'ParseTypeChange' + ]; diff --git a/awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js new file mode 100644 index 0000000000..1c3ba53d03 --- /dev/null +++ b/awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js @@ -0,0 +1,76 @@ +export default + function InventoryUpdate(PromptForPasswords, LaunchJob, Rest, GetBasePath, ProcessErrors, Alert, Wait) { + return function (params) { + + var scope = params.scope, + url = params.url, + inventory_source; + + if (scope.removeUpdateSubmitted) { + scope.removeUpdateSubmitted(); + } + scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () { + Wait('stop'); + if (scope.socketStatus === 'error') { + Alert('Sync Started', '
The request to start the inventory sync process was submitted. ' + + 'To monitor the status refresh the page by clicking the button.
', 'alert-info', null, null, null, null, true); + if (scope.refreshGroups) { + // inventory detail page + scope.refreshGroups(); + } + else if (scope.refresh) { + scope.refresh(); + } + } + }); + + if (scope.removePromptForPasswords) { + scope.removePromptForPasswords(); + } + scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { + PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' }); + }); + + if (scope.removeStartTheUpdate) { + scope.removeStartTheUpdate(); + } + scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { + LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); + }); + + // Check to see if we have permission to perform the update and if any passwords are needed + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + inventory_source = data; + if (data.can_update) { + if (data.passwords_needed_to_update) { + Wait('stop'); + scope.$emit('PromptForPasswords'); + } + else { + scope.$emit('StartTheUpdate', {}); + } + } else { + Wait('stop'); + Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.', + 'alert-danger'); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status }); + }); + }; + } + +InventoryUpdate.$inject = + [ 'PromptForPasswords', + 'LaunchJob', + 'Rest', + 'GetBasePath', + 'ProcessErrors', + 'Alert', + 'Wait' + ]; diff --git a/awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js new file mode 100644 index 0000000000..b089127600 --- /dev/null +++ b/awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js @@ -0,0 +1,78 @@ +export default + function ProjectUpdate(PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) { + return function (params) { + var scope = params.scope, + project_id = params.project_id, + url = GetBasePath('projects') + project_id + '/update/', + project; + + if (scope.removeUpdateSubmitted) { + scope.removeUpdateSubmitted(); + } + scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() { + // Refresh the project list after update request submitted + Wait('stop'); + if (/\d$/.test($location.path())) { + //Request submitted from projects/N page. Navigate back to the list so user can see status + $location.path('/projects'); + } + if (scope.socketStatus === 'error') { + Alert('Update Started', '
The request to start the SCM update process was submitted. ' + + 'To monitor the update status, refresh the page by clicking the button.
', 'alert-info', null, null, null, null, true); + if (scope.refresh) { + scope.refresh(); + } + } + }); + + if (scope.removePromptForPasswords) { + scope.removePromptForPasswords(); + } + scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { + PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' }); + }); + + if (scope.removeStartTheUpdate) { + scope.removeStartTheUpdate(); + } + scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { + LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); + }); + + // Check to see if we have permission to perform the update and if any passwords are needed + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + project = data; + if (project.can_update) { + if (project.passwords_needed_to_updated) { + Wait('stop'); + scope.$emit('PromptForPasswords'); + } + else { + scope.$emit('StartTheUpdate', {}); + } + } + else { + Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', + 'alert-danger'); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to lookup project ' + url + ' GET returned: ' + status }); + }); + }; + } + +ProjectUpdate.$inject = + [ 'PromptForPasswords', + 'LaunchJob', + 'Rest', + '$location', + 'GetBasePath', + 'ProcessErrors', + 'Alert', + 'Wait' + ]; diff --git a/awx/ui/client/src/job-submission/job-submission-factories/prompt-for-passwords.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/prompt-for-passwords.factory.js new file mode 100644 index 0000000000..7efc827919 --- /dev/null +++ b/awx/ui/client/src/job-submission/job-submission-factories/prompt-for-passwords.factory.js @@ -0,0 +1,82 @@ +export default + function PromptForPasswords(CredentialForm) { + return function(params) { + var scope = params.scope, + callback = params.callback || 'PasswordsAccepted', + url = params.url, + form = CredentialForm, + fld, field, + html=params.html || ""; + + scope.passwords = params.passwords; + + html += "
Launching this job requires the passwords listed below. Enter and confirm each password before continuing.
\n"; + + scope.passwords.forEach(function(password) { + // Prompt for password + field = form.fields[password]; + fld = password; + scope[fld] = ''; + html += "
\n"; + html += "\n"; + html += "Please enter a password.
\n"; + html += "
\n"; + html += "
\n"; + + // Add the related confirm field + if (field.associated) { + fld = field.associated; + field = form.fields[field.associated]; + scope[fld] = ''; + html += "
\n"; + html += "\n"; + html += "Please confirm the password.\n"; + html += (field.awPassMatch) ? "This value does not match the password you entered previously. Please confirm that password.
\n" : ""; + html += "
\n"; + html += "
\n"; + } + }); + + scope.$emit(callback, html, url); + + // Password change + scope.clearPWConfirm = function (fld) { + // If password value changes, make sure password_confirm must be re-entered + scope[fld] = ''; + scope.job_launch_form[fld].$setValidity('awpassmatch', false); + scope.checkStatus(); + }; + + scope.checkStatus = function() { + if (!scope.job_launch_form.$invalid) { + $('#password-accept-button').removeAttr('disabled'); + } + else { + $('#password-accept-button').attr({ "disabled": "disabled" }); + } + }; + }; + } + +PromptForPasswords.$inject = + [ 'CredentialForm' ]; diff --git a/awx/ui/client/src/job-submission/main.js b/awx/ui/client/src/job-submission/main.js index 71c4c5c0de..1204613560 100644 --- a/awx/ui/client/src/job-submission/main.js +++ b/awx/ui/client/src/job-submission/main.js @@ -7,6 +7,12 @@ import InitiatePlaybookRun from './job-submission-factories/initiateplaybookrun.factory'; import LaunchJob from './job-submission-factories/launchjob.factory'; import GetSurveyQuestions from './job-submission-factories/getsurveyquestions.factory'; +import AdhocRun from './job-submission-factories/adhoc-run.factory.js'; +import CheckPasswords from './job-submission-factories/check-passwords.factory'; +import CreateLaunchDialog from './job-submission-factories/create-launch-dialog.factory'; +import InventoryUpdate from './job-submission-factories/inventory-update.factory'; +import ProjectUpdate from './job-submission-factories/project-update.factory'; +import PromptForPasswords from './job-submission-factories/prompt-for-passwords.factory'; import submitJob from './job-submission.directive'; import credentialList from './lists/credential/job-sub-cred-list.directive'; import inventoryList from './lists/inventory/job-sub-inv-list.directive'; @@ -16,6 +22,12 @@ export default .factory('InitiatePlaybookRun', InitiatePlaybookRun) .factory('LaunchJob', LaunchJob) .factory('GetSurveyQuestions', GetSurveyQuestions) + .factory('AdhocRun', AdhocRun) + .factory('CheckPasswords', CheckPasswords) + .factory('CreateLaunchDialog', CreateLaunchDialog) + .factory('InventoryUpdate', InventoryUpdate) + .factory('ProjectUpdate', ProjectUpdate) + .factory('PromptForPasswords', PromptForPasswords) .directive('submitJob', submitJob) .directive('jobSubCredList', credentialList) .directive('jobSubInvList', inventoryList); diff --git a/awx/ui/client/src/jobs/factories/delete-job.factory.js b/awx/ui/client/src/jobs/factories/delete-job.factory.js new file mode 100644 index 0000000000..d82afe70fc --- /dev/null +++ b/awx/ui/client/src/jobs/factories/delete-job.factory.js @@ -0,0 +1,137 @@ +export default + function DeleteJob($state, Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert, + $filter, i18n) { + return function(params) { + var scope = params.scope, + id = params.id, + job = params.job, + callback = params.callback, + action, jobs, url, action_label, hdr; + + if (!job) { + if (scope.completed_jobs) { + jobs = scope.completed_jobs; + } + else if (scope.running_jobs) { + jobs = scope.running_jobs; + } + else if (scope.queued_jobs) { + jobs = scope.queued_jobs; + } + else if (scope.all_jobs) { + jobs = scope.all_jobs; + } + else if (scope.jobs) { + jobs = scope.jobs; + } + job = Find({list: jobs, key: 'id', val: id }); + } + + if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') { + url = job.related.cancel; + action_label = 'cancel'; + hdr = i18n._('Cancel'); + } else { + url = job.url; + action_label = 'delete'; + hdr = i18n._('Delete'); + } + + action = function () { + Wait('start'); + Rest.setUrl(url); + if (action_label === 'cancel') { + Rest.post() + .success(function () { + $('#prompt-modal').modal('hide'); + if (callback) { + scope.$emit(callback, action_label); + } + else { + $state.reload(); + Wait('stop'); + } + }) + .error(function(obj, status) { + Wait('stop'); + $('#prompt-modal').modal('hide'); + if (status === 403) { + Alert('Error', obj.detail); + } + // Ignore the error. The job most likely already finished. + // ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + + // ' failed. POST returned status: ' + status }); + }); + } else { + Rest.destroy() + .success(function () { + $('#prompt-modal').modal('hide'); + if (callback) { + scope.$emit(callback, action_label); + } + else { + $state.reload(); + Wait('stop'); + } + }) + .error(function (obj, status) { + Wait('stop'); + $('#prompt-modal').modal('hide'); + if (status === 403) { + Alert('Error', obj.detail); + } + // Ignore the error. The job most likely already finished. + //ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + + // ' failed. DELETE returned status: ' + status }); + }); + } + }; + + if (scope.removeCancelNotAllowed) { + scope.removeCancelNotAllowed(); + } + scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() { + Wait('stop'); + Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info'); + }); + + if (scope.removeCancelJob) { + scope.removeCancelJob(); + } + scope.removeCancelJob = scope.$on('CancelJob', function() { + var cancelBody = "
" + i18n._("Submit the request to cancel?") + "
"; + var deleteBody = "
" + i18n._("Are you sure you want to delete the job below?") + "
#" + id + " " + $filter('sanitize')(job.name) + "
"; + Prompt({ + hdr: hdr, + body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody, + action: action, + actionText: (action_label === 'cancel' || job.status === 'new') ? "OK" : "DELETE" + }); + }); + + if (action_label === 'cancel') { + Rest.setUrl(url); + Rest.get() + .success(function(data) { + if (data.can_cancel) { + scope.$emit('CancelJob'); + } + else { + scope.$emit('CancelNotAllowed'); + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + + ' failed. GET returned: ' + status }); + }); + } + else { + scope.$emit('CancelJob'); + } + }; + } + +DeleteJob.$inject = + [ '$state', 'Find', 'GetBasePath', 'Rest', 'Wait', + 'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n' + ]; diff --git a/awx/ui/client/src/jobs/factories/job-status-tool-tip.factory.js b/awx/ui/client/src/jobs/factories/job-status-tool-tip.factory.js new file mode 100644 index 0000000000..126c9977d3 --- /dev/null +++ b/awx/ui/client/src/jobs/factories/job-status-tool-tip.factory.js @@ -0,0 +1,31 @@ +export default + function JobStatusToolTip() { + return function(status) { + var toolTip; + switch (status) { + case 'successful': + case 'success': + toolTip = 'There were no failed tasks.'; + break; + case 'failed': + toolTip = 'Some tasks encountered errors.'; + break; + case 'canceled': + toolTip = 'Stopped by user request.'; + break; + case 'new': + toolTip = 'In queue, waiting on task manager.'; + break; + case 'waiting': + toolTip = 'SCM Update or Inventory Update is executing.'; + break; + case 'pending': + toolTip = 'Not in queue, waiting on task manager.'; + break; + case 'running': + toolTip = 'Playbook tasks executing.'; + break; + } + return toolTip; + }; + } diff --git a/awx/ui/client/src/jobs/factories/jobs-list-update.factory.js b/awx/ui/client/src/jobs/factories/jobs-list-update.factory.js new file mode 100644 index 0000000000..5f950e458c --- /dev/null +++ b/awx/ui/client/src/jobs/factories/jobs-list-update.factory.js @@ -0,0 +1,49 @@ +export default + function JobsListUpdate() { + return function(params) { + var scope = params.scope, + parent_scope = params.parent_scope, + list = params.list; + + scope[list.name].forEach(function(item, item_idx) { + var fld, field, + itm = scope[list.name][item_idx]; + + //if (item.type === 'inventory_update') { + // itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,''); + //} + + // Set the item type label + if (list.fields.type) { + parent_scope.type_choices.forEach(function(choice) { + if (choice.value === item.type) { + itm.type_label = choice.label; + } + }); + } + // Set the job status label + parent_scope.status_choices.forEach(function(status) { + if (status.value === item.status) { + itm.status_label = status.label; + } + }); + + if (list.name === 'completed_jobs' || list.name === 'running_jobs') { + itm.status_tip = itm.status_label + '. Click for details.'; + } + else if (list.name === 'queued_jobs') { + itm.status_tip = 'Pending'; + } + + // Copy summary_field values + for (field in list.fields) { + fld = list.fields[field]; + if (fld.sourceModel) { + if (itm.summary_fields[fld.sourceModel]) { + itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField]; + } + } + } + }); + }; + } diff --git a/awx/ui/client/src/jobs/factories/relaunch-adhoc.factory.js b/awx/ui/client/src/jobs/factories/relaunch-adhoc.factory.js new file mode 100644 index 0000000000..462501f097 --- /dev/null +++ b/awx/ui/client/src/jobs/factories/relaunch-adhoc.factory.js @@ -0,0 +1,11 @@ +export default + function RelaunchAdhoc(AdhocRun) { + return function(params) { + var scope = params.scope, + id = params.id; + AdhocRun({ scope: scope, project_id: id, relaunch: true }); + }; + } + +RelaunchAdhoc.$inject = + [ 'AdhocRun' ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-inventory.factory.js b/awx/ui/client/src/jobs/factories/relaunch-inventory.factory.js new file mode 100644 index 0000000000..eccd06a291 --- /dev/null +++ b/awx/ui/client/src/jobs/factories/relaunch-inventory.factory.js @@ -0,0 +1,30 @@ +export default + function RelaunchInventory(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) { + return function(params) { + var scope = params.scope, + id = params.id, + url = GetBasePath('inventory_sources') + id + '/'; + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + InventoryUpdate({ + scope: scope, + url: data.related.update, + group_name: data.summary_fields.group.name, + group_source: data.source, + tree_id: null, + group_id: data.group + }); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + + url + ' GET returned: ' + status }); + }); + }; + } + +RelaunchInventory.$inject = + [ 'Find', 'Wait', 'Rest', + 'InventoryUpdate', 'ProcessErrors', 'GetBasePath' + ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-job.factory.js b/awx/ui/client/src/jobs/factories/relaunch-job.factory.js new file mode 100644 index 0000000000..0190cf9c79 --- /dev/null +++ b/awx/ui/client/src/jobs/factories/relaunch-job.factory.js @@ -0,0 +1,28 @@ +export default + function RelaunchJob(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) { + return function(params) { + var scope = params.scope, + id = params.id, + type = params.type, + name = params.name; + if (type === 'inventory_update') { + RelaunchInventory({ scope: scope, id: id}); + } + else if (type === 'ad_hoc_command') { + RelaunchAdhoc({ scope: scope, id: id, name: name }); + } + else if (type === 'job' || type === 'system_job' || type === 'workflow_job') { + RelaunchPlaybook({ scope: scope, id: id, name: name, job_type: type }); + } + else if (type === 'project_update') { + RelaunchSCM({ scope: scope, id: id }); + } + }; + } + +RelaunchJob.$inject = + [ 'RelaunchInventory', + 'RelaunchPlaybook', + 'RelaunchSCM', + 'RelaunchAdhoc' + ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-playbook.factory.js b/awx/ui/client/src/jobs/factories/relaunch-playbook.factory.js new file mode 100644 index 0000000000..78431b17be --- /dev/null +++ b/awx/ui/client/src/jobs/factories/relaunch-playbook.factory.js @@ -0,0 +1,12 @@ +export default + function RelaunchPlaybook(InitiatePlaybookRun) { + return function(params) { + var scope = params.scope, + id = params.id, + job_type = params.job_type; + InitiatePlaybookRun({ scope: scope, id: id, relaunch: true, job_type: job_type }); + }; + } + +RelaunchPlaybook.$inject = + [ 'InitiatePlaybookRun' ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-scm.factory.js b/awx/ui/client/src/jobs/factories/relaunch-scm.factory.js new file mode 100644 index 0000000000..b37e90e9ca --- /dev/null +++ b/awx/ui/client/src/jobs/factories/relaunch-scm.factory.js @@ -0,0 +1,11 @@ +export default + function RelaunchSCM(ProjectUpdate) { + return function(params) { + var scope = params.scope, + id = params.id; + ProjectUpdate({ scope: scope, project_id: id }); + }; + } + +RelaunchSCM.$inject = + [ 'ProjectUpdate' ]; diff --git a/awx/ui/client/src/jobs/main.js b/awx/ui/client/src/jobs/main.js index b1782cead6..6045000ef0 100644 --- a/awx/ui/client/src/jobs/main.js +++ b/awx/ui/client/src/jobs/main.js @@ -6,10 +6,26 @@ import jobsList from './jobs-list.controller'; import jobsRoute from './jobs.route'; +import DeleteJob from './factories/delete-job.factory'; +import JobStatusToolTip from './factories/job-status-tool-tip.factory'; +import JobsListUpdate from './factories/jobs-list-update.factory'; +import RelaunchAdhoc from './factories/relaunch-adhoc.factory'; +import RelaunchInventory from './factories/relaunch-inventory.factory'; +import RelaunchJob from './factories/relaunch-job.factory'; +import RelaunchPlaybook from './factories/relaunch-playbook.factory'; +import RelaunchSCM from './factories/relaunch-scm.factory'; export default angular.module('JobsModule', []) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState(jobsRoute); }]) - .controller('JobsList', jobsList); + .controller('JobsList', jobsList) + .factory('DeleteJob', DeleteJob) + .factory('JobStatusToolTip', JobStatusToolTip) + .factory('JobsListUpdate', JobsListUpdate) + .factory('RelaunchAdhoc', RelaunchAdhoc) + .factory('RelaunchInventory', RelaunchInventory) + .factory('RelaunchJob', RelaunchJob) + .factory('RelaunchPlaybook', RelaunchPlaybook) + .factory('RelaunchSCM', RelaunchSCM); diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js index 0a59ddbaef..5db0ffeac2 100644 --- a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js @@ -12,9 +12,9 @@ */ export default ['$scope', '$rootScope', 'ProcessErrors', 'GetBasePath', 'generateList', -'SelectionInit', 'templateUrl', '$state', 'Rest', '$q', 'Wait', '$window', 'QuerySet', 'UserList', +'templateUrl', '$state', 'Rest', '$q', 'Wait', '$window', 'QuerySet', 'UserList', function($scope, $rootScope, ProcessErrors, GetBasePath, generateList, - SelectionInit, templateUrl, $state, Rest, $q, Wait, $window, qs, UserList) { + templateUrl, $state, Rest, $q, Wait, $window, qs, UserList) { $scope.$on("linkLists", function() { if ($state.current.name.split(".")[1] === "users") { diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index 7032ef5f81..5fa60c7ce8 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -7,11 +7,11 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'Prompt', 'ReturnToCaller', 'ClearScope', 'OrgProjectList', 'OrgProjectDataset', - 'ProcessErrors', 'GetBasePath', 'SelectionInit', 'ProjectUpdate', + 'ProcessErrors', 'GetBasePath', 'ProjectUpdate', 'Wait', 'GetChoices', 'Empty', 'Find', 'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state', function($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, Prompt, ReturnToCaller, ClearScope, OrgProjectList, Dataset, - ProcessErrors, GetBasePath, SelectionInit, ProjectUpdate, + ProcessErrors, GetBasePath, ProjectUpdate, Wait, GetChoices, Empty, Find, GetProjectIcon, GetProjectToolTip, $filter, $state) { var list = OrgProjectList, diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-teams.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-teams.controller.js index 654ed8ee59..3589328bb3 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-teams.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-teams.controller.js @@ -6,12 +6,12 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'OrgTeamList', 'Rest', 'Alert', 'Prompt', 'OrgTeamsDataset', 'ReturnToCaller', 'ClearScope', - 'ProcessErrors', 'SetTeamListeners', 'GetBasePath', - 'SelectionInit', 'Wait', '$state', + 'ProcessErrors', 'GetBasePath', + 'Wait', '$state', function($scope, $rootScope, $location, $log, $stateParams, OrgTeamList, Rest, Alert, Prompt, Dataset, ReturnToCaller, ClearScope, - ProcessErrors, SetTeamListeners, GetBasePath, - SelectionInit, Wait, $state) { + ProcessErrors, GetBasePath, + Wait, $state) { var list = OrgTeamList, orgBase = GetBasePath('organizations'); diff --git a/awx/ui/client/src/projects/factories/get-project-icon.factory.js b/awx/ui/client/src/projects/factories/get-project-icon.factory.js new file mode 100644 index 0000000000..5234041e38 --- /dev/null +++ b/awx/ui/client/src/projects/factories/get-project-icon.factory.js @@ -0,0 +1,30 @@ +export default + function GetProjectIcon() { + return function(status) { + var result = ''; + switch (status) { + case 'n/a': + case 'ok': + case 'never updated': + result = 'none'; + break; + case 'pending': + case 'waiting': + case 'new': + result = 'none'; + break; + case 'updating': + case 'running': + result = 'running'; + break; + case 'successful': + result = 'success'; + break; + case 'failed': + case 'missing': + case 'canceled': + result = 'error'; + } + return result; + }; + } diff --git a/awx/ui/client/src/projects/factories/get-project-path.factory.js b/awx/ui/client/src/projects/factories/get-project-path.factory.js new file mode 100644 index 0000000000..1a48772ff9 --- /dev/null +++ b/awx/ui/client/src/projects/factories/get-project-path.factory.js @@ -0,0 +1,78 @@ +export default + function GetProjectPath(Alert, Rest, GetBasePath, ProcessErrors) { + return function(params) { + var scope = params.scope, + master = params.master; + + function arraySort(data) { + //Sort nodes by name + var i, j, names = [], + newData = []; + for (i = 0; i < data.length; i++) { + names.push(data[i].value); + } + names.sort(); + for (j = 0; j < names.length; j++) { + for (i = 0; i < data.length; i++) { + if (data[i].value === names[j]) { + newData.push(data[i]); + } + } + } + return newData; + } + + scope.showMissingPlaybooksAlert = false; + + Rest.setUrl(GetBasePath('config')); + Rest.get() + .success(function (data) { + var opts = [], i; + if (data.project_local_paths) { + for (i = 0; i < data.project_local_paths.length; i++) { + opts.push({ + label: data.project_local_paths[i], + value: data.project_local_paths[i] + }); + } + } + if (scope.local_path) { + // List only includes paths not assigned to projects, so add the + // path assigned to the current project. + opts.push({ + label: scope.local_path, + value: scope.local_path + }); + } + scope.project_local_paths = arraySort(opts); + if (scope.local_path) { + for (i = 0; scope.project_local_paths.length; i++) { + if (scope.project_local_paths[i].value === scope.local_path) { + scope.local_path = scope.project_local_paths[i]; + break; + } + } + } + scope.base_dir = data.project_base_dir; + master.local_path = scope.local_path; + master.base_dir = scope.base_dir; // Keep in master object so that it doesn't get + // wiped out on form reset. + if (opts.length === 0) { + // trigger display of alert block when scm_type == manual + scope.showMissingPlaybooksAlert = true; + } + scope.$emit('pathsReady'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to access API config. GET status: ' + status }); + }); + }; + } + +GetProjectPath.$inject = + [ 'Alert', + 'Rest', + 'GetBasePath', + 'ProcessErrors' + ]; diff --git a/awx/ui/client/src/projects/factories/get-project-tool-tip.factory.js b/awx/ui/client/src/projects/factories/get-project-tool-tip.factory.js new file mode 100644 index 0000000000..6db397bed0 --- /dev/null +++ b/awx/ui/client/src/projects/factories/get-project-tool-tip.factory.js @@ -0,0 +1,38 @@ +export default + function GetProjectToolTip(i18n) { + return function(status) { + var result = ''; + switch (status) { + case 'n/a': + case 'ok': + case 'never updated': + result = i18n._('No SCM updates have run for this project'); + break; + case 'pending': + case 'waiting': + case 'new': + result = i18n._('Queued. Click for details'); + break; + case 'updating': + case 'running': + result = i18n._('Running! Click for details'); + break; + case 'successful': + result = i18n._('Success! Click for details'); + break; + case 'failed': + result = i18n._('Failed. Click for details'); + break; + case 'missing': + result = i18n._('Missing. Click for details'); + break; + case 'canceled': + result = i18n._('Canceled. Click for details'); + break; + } + return result; + }; + } + +GetProjectToolTip.$inject = + [ 'i18n' ]; diff --git a/awx/ui/client/src/projects/main.js b/awx/ui/client/src/projects/main.js index 05addaf1fe..2bb0c3cdcc 100644 --- a/awx/ui/client/src/projects/main.js +++ b/awx/ui/client/src/projects/main.js @@ -8,12 +8,18 @@ import ProjectsList from './list/projects-list.controller'; import ProjectsAdd from './add/projects-add.controller'; import ProjectsEdit from './edit/projects-edit.controller'; import { N_ } from '../i18n'; +import GetProjectPath from './factories/get-project-path.factory'; +import GetProjectIcon from './factories/get-project-icon.factory'; +import GetProjectToolTip from './factories/get-project-tool-tip.factory'; export default angular.module('Projects', []) .controller('ProjectsList', ProjectsList) .controller('ProjectsAdd', ProjectsAdd) .controller('ProjectsEdit', ProjectsEdit) + .factory('GetProjectPath', GetProjectPath) + .factory('GetProjectIcon', GetProjectIcon) + .factory('GetProjectToolTip', GetProjectToolTip) .config(['$stateProvider', 'stateDefinitionsProvider', function($stateProvider, stateDefinitionsProvider) { let stateDefinitions = stateDefinitionsProvider.$get(); diff --git a/awx/ui/client/src/scheduler/factories/add-schedule.factory.js b/awx/ui/client/src/scheduler/factories/add-schedule.factory.js new file mode 100644 index 0000000000..c1870dd469 --- /dev/null +++ b/awx/ui/client/src/scheduler/factories/add-schedule.factory.js @@ -0,0 +1,135 @@ +export default + function AddSchedule($location, $rootScope, $stateParams, SchedulerInit, + Wait, GetBasePath, Empty, SchedulePost, $state, Rest, + ProcessErrors) { + return function(params) { + var scope = params.scope, + callback= params.callback, + base = params.base || $location.path().replace(/^\//, '').split('/')[0], + url = params.url || null, + scheduler, + job_type; + + job_type = scope.parentObject.job_type; + if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !url) { + url = GetBasePath(base) + $stateParams.id + '/schedules/'; + } + else if(base === "inventories"){ + if (!params.url){ + url = GetBasePath('groups') + $stateParams.id + '/'; + Rest.setUrl(url); + Rest.get(). + then(function (data) { + url = data.data.related.inventory_source + 'schedules/'; + }).catch(function (response) { + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get inventory group info. GET returned status: ' + + response.status + }); + }); + } + else { + url = params.url; + } + } + else if (base === 'system_job_templates') { + url = GetBasePath(base) + $stateParams.id + '/schedules/'; + if(job_type === "cleanup_facts"){ + scope.isFactCleanup = true; + scope.keep_unit_choices = [{ + "label" : "Days", + "value" : "d" + }, + { + "label": "Weeks", + "value" : "w" + }, + { + "label" : "Years", + "value" : "y" + }]; + scope.granularity_keep_unit_choices = [{ + "label" : "Days", + "value" : "d" + }, + { + "label": "Weeks", + "value" : "w" + }, + { + "label" : "Years", + "value" : "y" + }]; + scope.prompt_for_days_facts_form.keep_amount.$setViewValue(30); + scope.prompt_for_days_facts_form.granularity_keep_amount.$setViewValue(1); + scope.keep_unit = scope.keep_unit_choices[0]; + scope.granularity_keep_unit = scope.granularity_keep_unit_choices[1]; + } + else { + scope.cleanupJob = true; + } + } + + Wait('start'); + $('#form-container').empty(); + scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); + if(scope.schedulerUTCTime) { + // The UTC time is already set + scope.processSchedulerEndDt(); + } + else { + // We need to wait for it to be set by angular-scheduler because the following function depends + // on it + var schedulerUTCTimeWatcher = scope.$watch('schedulerUTCTime', function(newVal) { + if(newVal) { + // Remove the watcher + schedulerUTCTimeWatcher(); + scope.processSchedulerEndDt(); + } + }); + } + scheduler.inject('form-container', false); + scheduler.injectDetail('occurrences', false); + scheduler.clear(); + scope.$on("htmlDetailReady", function() { + $rootScope.$broadcast("ScheduleFormCreated", scope); + }); + scope.showRRuleDetail = false; + + if (scope.removeScheduleSaved) { + scope.removeScheduleSaved(); + } + scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) { + Wait('stop'); + if (callback) { + scope.$emit(callback, data); + } + $state.go("^", null, {reload: true}); + }); + scope.saveSchedule = function() { + SchedulePost({ + scope: scope, + url: url, + scheduler: scheduler, + callback: 'ScheduleSaved', + mode: 'add' + }); + }; + + $('#scheduler-tabs li a').on('shown.bs.tab', function(e) { + if ($(e.target).text() === 'Details') { + if (!scheduler.isValid()) { + $('#scheduler-tabs a:first').tab('show'); + } + } + }); + }; + } + +AddSchedule.$inject = + [ '$location', '$rootScope', '$stateParams', + 'SchedulerInit', 'Wait', 'GetBasePath', + 'Empty', 'SchedulePost', '$state', + 'Rest', 'ProcessErrors' + ]; diff --git a/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js b/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js new file mode 100644 index 0000000000..7c75882966 --- /dev/null +++ b/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js @@ -0,0 +1,61 @@ +export default + function DeleteSchedule(GetBasePath, Rest, Wait, $state, + ProcessErrors, Prompt, Find, $location, $filter) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback, + action, schedule, list, url, hdr; + + if (scope.schedules) { + list = scope.schedules; + } + else if (scope.scheduled_jobs) { + list = scope.scheduled_jobs; + } + + url = GetBasePath('schedules') + id + '/'; + schedule = Find({list: list, key: 'id', val: id }); + hdr = 'Delete Schedule'; + + action = function () { + Wait('start'); + Rest.setUrl(url); + Rest.destroy() + .success(function () { + $('#prompt-modal').modal('hide'); + scope.$emit(callback, id); + if (new RegExp('/' + id + '$').test($location.$$url)) { + $location.url($location.url().replace(/[/][0-9]+$/, "")); // go to list view + } + else{ + $state.go('.', null, {reload: true}); + } + }) + .error(function (data, status) { + try { + $('#prompt-modal').modal('hide'); + } + catch(e) { + // ignore + } + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + + ' failed. DELETE returned: ' + status }); + }); + }; + + Prompt({ + hdr: hdr, + body: '
Are you sure you want to delete the schedule below?
' + $filter('sanitize')(schedule.name) + '
', + action: action, + actionText: 'DELETE', + backdrop: false + }); + }; + } + +DeleteSchedule.$inject = + [ 'GetBasePath','Rest', 'Wait', '$state', + 'ProcessErrors', 'Prompt', 'Find', '$location', + '$filter' + ]; diff --git a/awx/ui/client/src/scheduler/factories/edit-schedule.factory.js b/awx/ui/client/src/scheduler/factories/edit-schedule.factory.js new file mode 100644 index 0000000000..de591aee08 --- /dev/null +++ b/awx/ui/client/src/scheduler/factories/edit-schedule.factory.js @@ -0,0 +1,154 @@ +export default + function EditSchedule(SchedulerInit, $rootScope, Wait, Rest, ProcessErrors, + GetBasePath, SchedulePost, $state) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback, + schedule, scheduler, + url = GetBasePath('schedules') + id + '/'; + + delete scope.isFactCleanup; + delete scope.cleanupJob; + + function setGranularity(){ + var a,b, prompt_for_days, + keep_unit, + granularity, + granularity_keep_unit; + + if(scope.cleanupJob){ + scope.schedulerPurgeDays = Number(schedule.extra_data.days); + // scope.scheduler_form.schedulerPurgeDays.$setViewValue( Number(schedule.extra_data.days)); + } + else if(scope.isFactCleanup){ + scope.keep_unit_choices = [{ + "label" : "Days", + "value" : "d" + }, + { + "label": "Weeks", + "value" : "w" + }, + { + "label" : "Years", + "value" : "y" + }]; + scope.granularity_keep_unit_choices = [{ + "label" : "Days", + "value" : "d" + }, + { + "label": "Weeks", + "value" : "w" + }, + { + "label" : "Years", + "value" : "y" + }]; + // the API returns something like 20w or 1y + a = schedule.extra_data.older_than; // "20y" + b = schedule.extra_data.granularity; // "1w" + prompt_for_days = Number(_.initial(a,1).join('')); // 20 + keep_unit = _.last(a); // "y" + granularity = Number(_.initial(b,1).join('')); // 1 + granularity_keep_unit = _.last(b); // "w" + + scope.keep_amount = prompt_for_days; + scope.granularity_keep_amount = granularity; + scope.keep_unit = _.find(scope.keep_unit_choices, function(i){ + return i.value === keep_unit; + }); + scope.granularity_keep_unit =_.find(scope.granularity_keep_unit_choices, function(i){ + return i.value === granularity_keep_unit; + }); + } + } + + if (scope.removeScheduleFound) { + scope.removeScheduleFound(); + } + scope.removeScheduleFound = scope.$on('ScheduleFound', function() { + $('#form-container').empty(); + scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); + scheduler.inject('form-container', false); + scheduler.injectDetail('occurrences', false); + + if (!/DTSTART/.test(schedule.rrule)) { + schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z'); + } + schedule.rrule = schedule.rrule.replace(/ RRULE:/,';'); + schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART='); + scope.$on("htmlDetailReady", function() { + scheduler.setRRule(schedule.rrule); + scheduler.setName(schedule.name); + $rootScope.$broadcast("ScheduleFormCreated", scope); + }); + scope.showRRuleDetail = false; + + scheduler.setRRule(schedule.rrule); + scheduler.setName(schedule.name); + if(scope.isFactCleanup || scope.cleanupJob){ + setGranularity(); + } + }); + + + if (scope.removeScheduleSaved) { + scope.removeScheduleSaved(); + } + scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) { + Wait('stop'); + if (callback) { + scope.$emit(callback, data); + } + $state.go("^"); + }); + scope.saveSchedule = function() { + schedule.extra_data = scope.extraVars; + SchedulePost({ + scope: scope, + url: url, + scheduler: scheduler, + callback: 'ScheduleSaved', + mode: 'edit', + schedule: schedule + }); + }; + + Wait('start'); + + // Get the existing record + Rest.setUrl(url); + Rest.get() + .success(function(data) { + schedule = data; + try { + schedule.extra_data = JSON.parse(schedule.extra_data); + } catch(e) { + // do nothing + } + scope.extraVars = data.extra_data === '' ? '---' : '---\n' + jsyaml.safeDump(data.extra_data); + + if(schedule.extra_data.hasOwnProperty('granularity')){ + scope.isFactCleanup = true; + } + if (schedule.extra_data.hasOwnProperty('days')){ + scope.cleanupJob = true; + } + + scope.schedule_obj = data; + + scope.$emit('ScheduleFound'); + }) + .error(function(data,status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); + }); + }; + } + +EditSchedule.$inject = + [ 'SchedulerInit', '$rootScope', 'Wait', 'Rest', + 'ProcessErrors', 'GetBasePath', 'SchedulePost', '$state' + ]; diff --git a/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js b/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js new file mode 100644 index 0000000000..1e3b251d8a --- /dev/null +++ b/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js @@ -0,0 +1,10 @@ +export default + function RRuleToAPI() { + return function(rrule) { + var response; + response = rrule.replace(/(^.*(?=DTSTART))(DTSTART=.*?;)(.*$)/, function(str, p1, p2, p3) { + return p2.replace(/\;/,'').replace(/=/,':') + ' ' + 'RRULE:' + p1 + p3; + }); + return response; + }; + } diff --git a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js new file mode 100644 index 0000000000..2c636f8b89 --- /dev/null +++ b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js @@ -0,0 +1,78 @@ +export default + function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait) { + return function(params) { + var scope = params.scope, + url = params.url, + scheduler = params.scheduler, + mode = params.mode, + schedule = (params.schedule) ? params.schedule : {}, + callback = params.callback, + newSchedule, rrule, extra_vars; + if (scheduler.isValid()) { + Wait('start'); + newSchedule = scheduler.getValue(); + rrule = scheduler.getRRule(); + schedule.name = newSchedule.name; + schedule.rrule = RRuleToAPI(rrule.toString()); + schedule.description = (/error/.test(rrule.toText())) ? '' : rrule.toText(); + + if (scope.isFactCleanup) { + extra_vars = { + "older_than": scope.scheduler_form.keep_amount.$viewValue + scope.scheduler_form.keep_unit.$viewValue.value, + "granularity": scope.scheduler_form.granularity_keep_amount.$viewValue + scope.scheduler_form.granularity_keep_unit.$viewValue.value + }; + schedule.extra_data = JSON.stringify(extra_vars); + } else if (scope.cleanupJob) { + extra_vars = { + "days" : scope.scheduler_form.schedulerPurgeDays.$viewValue + }; + schedule.extra_data = JSON.stringify(extra_vars); + } + else if(scope.extraVars){ + schedule.extra_data = scope.parseType === 'yaml' ? + (scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars; + } + Rest.setUrl(url); + if (mode === 'add') { + Rest.post(schedule) + .success(function(){ + if (callback) { + scope.$emit(callback); + } + else { + Wait('stop'); + } + }) + .error(function(data, status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'POST to ' + url + ' returned: ' + status }); + }); + } + else { + Rest.put(schedule) + .success(function(){ + if (callback) { + scope.$emit(callback, schedule); + } + else { + Wait('stop'); + } + }) + .error(function(data, status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'POST to ' + url + ' returned: ' + status }); + }); + } + } + else { + return false; + } + }; + } + +SchedulePost.$inject = + [ 'Rest', + 'ProcessErrors', + 'RRuleToAPI', + 'Wait' + ]; diff --git a/awx/ui/client/src/scheduler/factories/toggle-schedule.factory.js b/awx/ui/client/src/scheduler/factories/toggle-schedule.factory.js new file mode 100644 index 0000000000..423c92227a --- /dev/null +++ b/awx/ui/client/src/scheduler/factories/toggle-schedule.factory.js @@ -0,0 +1,46 @@ +export default + function ToggleSchedule(Wait, GetBasePath, ProcessErrors, Rest, $state) { + return function(params) { + var scope = params.scope, + id = params.id, + url = GetBasePath('schedules') + id +'/'; + + // Perform the update + if (scope.removeScheduleFound) { + scope.removeScheduleFound(); + } + scope.removeScheduleFound = scope.$on('ScheduleFound', function(e, data) { + data.enabled = (data.enabled) ? false : true; + Rest.put(data) + .success( function() { + Wait('stop'); + $state.go('.', null, {reload: true}); + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status }); + }); + }); + + Wait('start'); + + // Get the schedule + Rest.setUrl(url); + Rest.get() + .success(function(data) { + scope.$emit('ScheduleFound', data); + }) + .error(function(data,status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); + }); + }; + } + +ToggleSchedule.$inject = + [ 'Wait', + 'GetBasePath', + 'ProcessErrors', + 'Rest', + '$state' + ]; diff --git a/awx/ui/client/src/scheduler/main.js b/awx/ui/client/src/scheduler/main.js index 05960b13d6..81ad13d3c3 100644 --- a/awx/ui/client/src/scheduler/main.js +++ b/awx/ui/client/src/scheduler/main.js @@ -10,12 +10,24 @@ import editController from './schedulerEdit.controller'; import {templateUrl} from '../shared/template-url/template-url.factory'; import schedulerDatePicker from './schedulerDatePicker.directive'; import { N_ } from '../i18n'; +import AddSchedule from './factories/add-schedule.factory'; +import DeleteSchedule from './factories/delete-schedule.factory'; +import EditSchedule from './factories/edit-schedule.factory'; +import RRuleToAPI from './factories/r-rule-to-api.factory'; +import SchedulePost from './factories/schedule-post.factory'; +import ToggleSchedule from './factories/toggle-schedule.factory'; export default angular.module('scheduler', []) .controller('schedulerListController', listController) .controller('schedulerAddController', addController) .controller('schedulerEditController', editController) + .factory('AddSchedule', AddSchedule) + .factory('DeleteSchedule', DeleteSchedule) + .factory('EditSchedule', EditSchedule) + .factory('RRuleToAPI', RRuleToAPI) + .factory('SchedulePost', SchedulePost) + .factory('ToggleSchedule', ToggleSchedule) .directive('schedulerDatePicker', schedulerDatePicker) .run(['$stateExtender', function($stateExtender) { // Inventory sync schedule states registered in: awx/ui/client/src/inventories/manage/groups/main.js diff --git a/awx/ui/client/src/shared/Modal.js b/awx/ui/client/src/shared/Modal.js index e6890ac367..86c2da80a9 100644 --- a/awx/ui/client/src/shared/Modal.js +++ b/awx/ui/client/src/shared/Modal.js @@ -17,7 +17,7 @@ export default -angular.module('ModalDialog', ['Utilities', 'ParseHelper']) +angular.module('ModalDialog', ['Utilities']) /** * @ngdoc method diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index 1dcfb7ac62..9c09f2f4ed 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -14,7 +14,7 @@ */ export default -angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) +angular.module('AWDirectives', ['RestServices', 'Utilities']) // awpassmatch: Add to password_confirm field. Will test if value // matches that of 'input[name="password"]' diff --git a/awx/ui/client/src/helpers/LoadConfig.js b/awx/ui/client/src/shared/load-config/load-config.factory.js similarity index 82% rename from awx/ui/client/src/helpers/LoadConfig.js rename to awx/ui/client/src/shared/load-config/load-config.factory.js index 93d381079b..6394acd288 100644 --- a/awx/ui/client/src/helpers/LoadConfig.js +++ b/awx/ui/client/src/shared/load-config/load-config.factory.js @@ -1,30 +1,5 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:LoadConfig - * @description Attempts to load local_config.js. If not found, loads config.js. Then evaluates the loaded - * javascript, putting the result in $AnsibleConfig. - * LoadConfigHelper - * - * - * - */ - -/*jshint evil:true */ - - - export default -angular.module('LoadConfigHelper', ['Utilities']) - -.factory('LoadConfig', ['$log', '$rootScope', '$http', '$location', 'GetBasePath', - 'ProcessErrors', 'Rest', 'Store', - function($log, $rootScope, $http, $location, GetBasePath, ProcessErrors, Rest, Store) { + function LoadConfig($log, $rootScope, $http, $location, GetBasePath, ProcessErrors, Rest, Store) { return function() { // These ettings used to be found in config.js, hardcoded now. @@ -105,4 +80,8 @@ angular.module('LoadConfigHelper', ['Utilities']) }; } -]); + +LoadConfig.$inject = + [ '$log', '$rootScope', '$http', '$location', + 'GetBasePath', 'ProcessErrors', 'Rest', 'Store' + ]; diff --git a/awx/ui/client/src/shared/load-config/main.js b/awx/ui/client/src/shared/load-config/main.js new file mode 100644 index 0000000000..3accf42ffa --- /dev/null +++ b/awx/ui/client/src/shared/load-config/main.js @@ -0,0 +1,5 @@ +import LoadConfig from './load-config.factory'; + +export default + angular.module('loadconfig', []) + .factory('LoadConfig', LoadConfig); diff --git a/awx/ui/client/src/shared/parse/main.js b/awx/ui/client/src/shared/parse/main.js new file mode 100644 index 0000000000..962bf14bcf --- /dev/null +++ b/awx/ui/client/src/shared/parse/main.js @@ -0,0 +1,5 @@ +import ParseTypeChange from './parse-type-change.factory'; + +export default + angular.module('parse', []) + .factory('ParseTypeChange', ParseTypeChange); diff --git a/awx/ui/client/src/shared/parse/parse-type-change.factory.js b/awx/ui/client/src/shared/parse/parse-type-change.factory.js new file mode 100644 index 0000000000..678c91abe9 --- /dev/null +++ b/awx/ui/client/src/shared/parse/parse-type-change.factory.js @@ -0,0 +1,93 @@ +import 'codemirror/lib/codemirror.js'; +import 'codemirror/mode/javascript/javascript.js'; +import 'codemirror/mode/yaml/yaml.js'; +import 'codemirror/addon/lint/lint.js'; +import 'angular-codemirror/lib/yaml-lint.js'; +import 'codemirror/addon/edit/closebrackets.js'; +import 'codemirror/addon/edit/matchbrackets.js'; +import 'codemirror/addon/selection/active-line.js'; + +export default + function ParseTypeChange(Alert, AngularCodeMirror) { + return function(params) { + var scope = params.scope, + field_id = params.field_id, + fld = (params.variable) ? params.variable : 'variables', + pfld = (params.parse_variable) ? params.parse_variable : 'parseType', + onReady = params.onReady, + onChange = params.onChange, + readOnly = params.readOnly; + + function removeField(fld) { + //set our model to the last change in CodeMirror and then destroy CodeMirror + scope[fld] = scope[fld + 'codeMirror'].getValue(); + $('#cm-' + fld + '-container > .CodeMirror').empty().remove(); + } + + function createField(onChange, onReady, fld) { + //hide the textarea and show a fresh CodeMirror with the current mode (json or yaml) + + scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly); + scope[fld + 'codeMirror'].addModes(global.$AnsibleConfig.variable_edit_modes); + scope[fld + 'codeMirror'].showTextArea({ + scope: scope, + model: fld, + element: field_id, + lineNumbers: true, + mode: scope[pfld], + onReady: onReady, + onChange: onChange + }); + } + + // Hide the textarea and show a CodeMirror editor + createField(onChange, onReady, fld); + + + // Toggle displayed variable string between JSON and YAML + scope.parseTypeChange = function(model, fld) { + var json_obj; + if (scope[model] === 'json') { + // converting yaml to json + try { + removeField(fld); + json_obj = jsyaml.load(scope[fld]); + if ($.isEmptyObject(json_obj)) { + scope[fld] = "{}"; + } + else { + scope[fld] = JSON.stringify(json_obj, null, " "); + } + createField(onReady, onChange, fld); + } + catch (e) { + Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message); + setTimeout( function() { scope.$apply( function() { scope[model] = 'yaml'; createField(onReady, onChange, fld); }); }, 500); + } + } + else { + // convert json to yaml + try { + removeField(fld); + json_obj = JSON.parse(scope[fld]); + if ($.isEmptyObject(json_obj)) { + scope[fld] = '---'; + } + else { + scope[fld] = jsyaml.safeDump(json_obj); + } + createField(onReady, onChange, fld); + } + catch (e) { + Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message); + setTimeout( function() { scope.$apply( function() { scope[model] = 'json'; createField(onReady, onChange, fld); }); }, 500 ); + } + } + }; + }; + } + +ParseTypeChange.$inject = + [ 'Alert', + 'AngularCodeMirror' + ]; diff --git a/awx/ui/client/src/shared/variables/main.js b/awx/ui/client/src/shared/variables/main.js new file mode 100644 index 0000000000..29942a7ff9 --- /dev/null +++ b/awx/ui/client/src/shared/variables/main.js @@ -0,0 +1,9 @@ +import ParseVariableString from './parse-variable-string.factory'; +import SortVariables from './sort-variables.factory'; +import ToJSON from './to-json.factory'; + +export default + angular.module('variables', []) + .factory('ParseVariableString', ParseVariableString) + .factory('SortVariables', SortVariables) + .factory('ToJSON', ToJSON); diff --git a/awx/ui/client/src/shared/variables/parse-variable-string.factory.js b/awx/ui/client/src/shared/variables/parse-variable-string.factory.js new file mode 100644 index 0000000000..71e145897f --- /dev/null +++ b/awx/ui/client/src/shared/variables/parse-variable-string.factory.js @@ -0,0 +1,55 @@ +export default + function ParseVariableString($log, ProcessErrors, SortVariables) { + return function (variables) { + var result = "---", json_obj; + if (typeof variables === 'string') { + if (variables === "{}" || variables === "null" || variables === "" || variables === "\"\"") { + // String is empty, return --- + } else { + try { + json_obj = JSON.parse(variables); + json_obj = SortVariables(json_obj); + result = jsyaml.safeDump(json_obj); + + } + catch (e) { + $log.debug('Attempt to parse extra_vars as JSON failed. Check that the variables parse as yaml. Set the raw string as the result.'); + try { + // do safeLoad, which well error if not valid yaml + json_obj = jsyaml.safeLoad(variables); + // but just send the variables + result = variables; + } + catch(e2) { + ProcessErrors(null, variables, e2.message, null, { hdr: 'Error!', + msg: 'Attempts to parse variables as JSON and YAML failed. Last attempt returned: ' + e2.message }); + } + } + } + } + else { + if ($.isEmptyObject(variables) || variables === null) { + // Empty object, return --- + } + else { + // convert object to yaml + try { + json_obj = SortVariables(variables); + result = jsyaml.safeDump(json_obj); + // result = variables; + } + catch(e3) { + ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!', + msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message }); + } + } + } + return result; + }; + } + +ParseVariableString.$inject = + [ '$log', + 'ProcessErrors', + 'SortVariables' + ]; diff --git a/awx/ui/client/src/shared/variables/sort-variables.factory.js b/awx/ui/client/src/shared/variables/sort-variables.factory.js new file mode 100644 index 0000000000..a9cb3a363b --- /dev/null +++ b/awx/ui/client/src/shared/variables/sort-variables.factory.js @@ -0,0 +1,23 @@ +export default + function SortVariables() { + return function(variableObj) { + var newObj; + function sortIt(objToSort) { + var i, + keys = Object.keys(objToSort), + newObj = {}; + keys = keys.sort(); + for (i=0; i < keys.length; i++) { + if (typeof objToSort[keys[i]] === 'object' && objToSort[keys[i]] !== null && !Array.isArray(objToSort[keys[i]])) { + newObj[keys[i]] = sortIt(objToSort[keys[i]]); + } + else { + newObj[keys[i]] = objToSort[keys[i]]; + } + } + return newObj; + } + newObj = sortIt(variableObj); + return newObj; + }; + } diff --git a/awx/ui/client/src/shared/variables/to-json.factory.js b/awx/ui/client/src/shared/variables/to-json.factory.js new file mode 100644 index 0000000000..70996d489e --- /dev/null +++ b/awx/ui/client/src/shared/variables/to-json.factory.js @@ -0,0 +1,80 @@ +export default + function ToJSON($log, ProcessErrors) { + return function(parseType, variables, stringify, reviver) { + var json_data, + result, + tmp; + // bracketVar, + // key, + // lines, i, newVars = []; + if (parseType === 'json') { + try { + // perform a check to see if the user cleared the field completly + if(variables.trim() === "" || variables.trim() === "{" || variables.trim() === "}" ){ + variables = "{}"; + } + //parse a JSON string + if (reviver) { + json_data = JSON.parse(variables, reviver); + } + else { + json_data = JSON.parse(variables); + } + } + catch(e) { + json_data = {}; + $log.error('Failed to parse JSON string. Parser returned: ' + e.message); + ProcessErrors(null, variables, e.message, null, { hdr: 'Error!', + msg: 'Failed to parse JSON string. Parser returned: ' + e.message }); + throw 'Parse error. Failed to parse variables.'; + } + } else { + try { + if(variables.trim() === "" || variables.trim() === "-" || variables.trim() === "--"){ + variables = '---'; + } + json_data = jsyaml.safeLoad(variables); + if(json_data!==null){ + // unparsing just to make sure no weird characters are included. + tmp = jsyaml.dump(json_data); + if(tmp.indexOf('[object Object]')!==-1){ + throw "Failed to parse YAML string. Parser returned' + key + ' : ' +value + '.' "; + } + } + } + catch(e) { + json_data = undefined; // {}; + // $log.error('Failed to parse YAML string. Parser returned undefined'); + ProcessErrors(null, variables, e.message, null, { hdr: 'Error!', + msg: 'Failed to parse YAML string. Parser returned undefined'}); + } + } + // Make sure our JSON is actually an object + if (typeof json_data !== 'object') { + ProcessErrors(null, variables, null, null, { hdr: 'Error!', + msg: 'Failed to parse variables. Attempted to parse ' + parseType + '. Parser did not return an object.' }); + // setTimeout( function() { + throw 'Parse error. Failed to parse variables.'; + // }, 1000); + } + result = json_data; + if (stringify) { + if(json_data === undefined){ + result = undefined; + } + else if ($.isEmptyObject(json_data)) { + result = ""; + } else { + // utilize the parsing to get here + // but send the raw variable string + result = variables; + } + } + return result; + }; + } + +ToJSON.$inject = + [ '$log', + 'ProcessErrors' + ]; diff --git a/awx/ui/client/src/templates/job_templates/factories/callback-help-init.factory.js b/awx/ui/client/src/templates/job_templates/factories/callback-help-init.factory.js new file mode 100644 index 0000000000..891d391f95 --- /dev/null +++ b/awx/ui/client/src/templates/job_templates/factories/callback-help-init.factory.js @@ -0,0 +1,156 @@ +export default + function CallbackHelpInit($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors, ParseTypeChange, + ParseVariableString, Empty, InventoryList, CredentialList, ProjectList, Wait) { + return function(params) { + var scope = params.scope, + defaultUrl = GetBasePath('job_templates'), + // generator = GenerateForm, + form = JobTemplateForm(), + // loadingFinishedCount = 0, + // base = $location.path().replace(/^\//, '').split('/')[0], + master = {}, + id = $stateParams.job_template_id; + // checkSCMStatus, getPlaybooks, callback, + // choicesCount = 0; + + CredentialList = _.cloneDeep(CredentialList); + + // The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the + // popover is activated, a function checks the value of scope.callback_help before constructing the content. + scope.setCallbackHelp = function() { + scope.callback_help = "

With a provisioning callback URL and a host config key a host can contact Tower and request a configuration update using this job " + + "template. The request from the host must be a POST. Here is an example using curl:

\n" + + "
curl --data \"host_config_key=" + scope.example_config_key + "\" " +
+                    scope.callback_server_path + GetBasePath('job_templates') + scope.example_template_id + "/callback/
\n" + + "

Note the requesting host must be defined in the inventory associated with the job template. If Tower fails to " + + "locate the host, the request will be denied.

" + + "

Successful requests create an entry on the Jobs page, where results and history can be viewed.

"; + }; + + // The md5 helper emits NewMD5Generated whenever a new key is available + if (scope.removeNewMD5Generated) { + scope.removeNewMD5Generated(); + } + scope.removeNewMD5Generated = scope.$on('NewMD5Generated', function() { + scope.configKeyChange(); + }); + + // Fired when user enters a key value + scope.configKeyChange = function() { + scope.example_config_key = scope.host_config_key; + scope.setCallbackHelp(); + }; + + // Set initial values and construct help text + scope.callback_server_path = $location.protocol() + '://' + $location.host() + (($location.port()) ? ':' + $location.port() : ''); + scope.example_config_key = '5a8ec154832b780b9bdef1061764ae5a'; + scope.example_template_id = 'N'; + scope.setCallbackHelp(); + + // this fills the job template form both on copy of the job template + // and on edit + scope.fillJobTemplate = function(){ + // id = id || $rootScope.copy.id; + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl + id); + Rest.get() + .success(function (data) { + scope.job_template_obj = data; + scope.name = data.name; + var fld, i; + for (fld in form.fields) { + if (fld !== 'variables' && fld !== 'survey' && data[fld] !== null && data[fld] !== undefined) { + if (form.fields[fld].type === 'select') { + if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) { + for (i = 0; i < scope[fld + '_options'].length; i++) { + if (data[fld] === scope[fld + '_options'][i].value) { + scope[fld] = scope[fld + '_options'][i]; + } + } + } else { + scope[fld] = data[fld]; + } + } else { + scope[fld] = data[fld]; + if(!Empty(data.summary_fields.survey)) { + scope.survey_exists = true; + } + } + master[fld] = scope[fld]; + } + if (fld === 'variables') { + // Parse extra_vars, converting to YAML. + scope.variables = ParseVariableString(data.extra_vars); + master.variables = scope.variables; + } + if (form.fields[fld].type === 'lookup' && 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] = + scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; + } + if (form.fields[fld].type === 'checkbox_group') { + for(var j=0; j Date: Tue, 28 Feb 2017 16:30:57 -0500 Subject: [PATCH 09/39] Fixed unit tests --- .../workflow-results.controller-test.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/awx/ui/tests/spec/workflow--results/workflow-results.controller-test.js b/awx/ui/tests/spec/workflow--results/workflow-results.controller-test.js index 90bc6f582f..91616b8a03 100644 --- a/awx/ui/tests/spec/workflow--results/workflow-results.controller-test.js +++ b/awx/ui/tests/spec/workflow--results/workflow-results.controller-test.js @@ -7,7 +7,6 @@ describe('Controller: workflowResults', () => { let $controller; let workflowResults; let $rootScope; - let ParseVariableString; let workflowResultsService; let $interval; @@ -17,10 +16,8 @@ describe('Controller: workflowResults', () => { } }; - beforeEach(angular.mock.module('VariablesHelper')); - beforeEach(angular.mock.module('workflowResults', ($provide) => { - ['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors', + ['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', 'jobLabels', 'workflowNodes', 'count', ].forEach((item) => { $provide.value(item, {}); @@ -30,8 +27,9 @@ describe('Controller: workflowResults', () => { $provide.value('workflowData', workflow_job_json); $provide.value('workflowDataOptions', workflow_job_options_json); $provide.value('ParseTypeChange', function() {}); + $provide.value('ParseVariableString', function() {}); $provide.value('i18n', { '_': (a) => { return a; } }); - $provide.provider('$stateProvider', { '$get': function() { return function() {} } }); + $provide.provider('$stateProvider', { '$get': function() { return function() {}; } }); $provide.service('WorkflowService', function($q) { return { buildTree: function() { @@ -39,14 +37,13 @@ describe('Controller: workflowResults', () => { deferred.resolve(treeData); return deferred.promise; } - } + }; }); })); - beforeEach(angular.mock.inject(function(_$controller_, _$rootScope_, _ParseVariableString_, _workflowResultsService_, _$interval_){ + beforeEach(angular.mock.inject(function(_$controller_, _$rootScope_, _workflowResultsService_, _$interval_){ $controller = _$controller_; $rootScope = _$rootScope_; - ParseVariableString = _ParseVariableString_; workflowResultsService = _workflowResultsService_; $interval = _$interval_; @@ -102,7 +99,7 @@ describe('Controller: workflowResults', () => { describe('job waiting', () => { beforeEach(() => { jobWaitingWorkflowResultsControllerFixture(null, 'waiting'); - }); + }); it('should not start elapsed timer', () => { expect(workflowResultsService.createOneSecondTimer).not.toHaveBeenCalled(); @@ -133,4 +130,4 @@ describe('Controller: workflowResults', () => { }); }); }); -}); \ No newline at end of file +}); From f90ba8e6a61e7ed3ff8609c2f1c66452cb16ce62 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 27 Feb 2017 12:39:05 -0500 Subject: [PATCH 10/39] small refactor of python requirement test to work in dev env --- .../functional/test_python_requirements.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/awx/main/tests/functional/test_python_requirements.py b/awx/main/tests/functional/test_python_requirements.py index 0dc48f66b8..cf937786b2 100644 --- a/awx/main/tests/functional/test_python_requirements.py +++ b/awx/main/tests/functional/test_python_requirements.py @@ -13,14 +13,21 @@ def test_env_matches_requirements_txt(): return False return True + def skip_line(line): + return ( + line == '' or line.strip().startswith('#') or + line.strip().startswith('git') or line.startswith('-e') or + '## The following requirements were added by pip freeze' in line + ) + base_dir = settings.BASE_DIR requirements_path = os.path.join(base_dir, '../', 'requirements/requirements.txt') reqs_actual = [] xs = freeze.freeze(local_only=True) for x in xs: - if '## The following requirements were added by pip freeze' in x: - break + if skip_line(x): + continue x = x.lower() (pkg_name, pkg_version) = x.split('==') reqs_actual.append([pkg_name, pkg_version]) @@ -31,11 +38,7 @@ def test_env_matches_requirements_txt(): line = line.partition('#')[0] line = line.rstrip().lower() # TODO: process git requiremenst and use egg - if line == '': - continue - if line.strip().startswith('#') or line.strip().startswith('git'): - continue - if line.startswith('-e'): + if skip_line(line): continue ''' From 239d3001d187a5e5253e8b0c217913e8b992d021 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 1 Mar 2017 11:48:26 -0500 Subject: [PATCH 11/39] Widgets dir cleanup --- .../factories/build-anchor.factory.js | 78 +++++ .../factories/build-description.factory.js | 126 +++++++ .../factories/show-detail.factory.js | 39 +++ .../factories/stream.factory.js | 54 +++ awx/ui/client/src/activity-stream/main.js | 10 +- awx/ui/client/src/app.js | 1 - awx/ui/client/src/widgets.js | 11 - .../client/src/widgets/InventorySyncStatus.js | 115 ------- awx/ui/client/src/widgets/JobStatus.js | 107 ------ awx/ui/client/src/widgets/ObjectCount.js | 78 ----- awx/ui/client/src/widgets/SCMSyncStatus.js | 108 ------ awx/ui/client/src/widgets/Stream.js | 317 ------------------ 12 files changed, 305 insertions(+), 739 deletions(-) create mode 100644 awx/ui/client/src/activity-stream/factories/build-anchor.factory.js create mode 100644 awx/ui/client/src/activity-stream/factories/build-description.factory.js create mode 100644 awx/ui/client/src/activity-stream/factories/show-detail.factory.js create mode 100644 awx/ui/client/src/activity-stream/factories/stream.factory.js delete mode 100644 awx/ui/client/src/widgets.js delete mode 100644 awx/ui/client/src/widgets/InventorySyncStatus.js delete mode 100644 awx/ui/client/src/widgets/JobStatus.js delete mode 100644 awx/ui/client/src/widgets/ObjectCount.js delete mode 100644 awx/ui/client/src/widgets/SCMSyncStatus.js delete mode 100644 awx/ui/client/src/widgets/Stream.js diff --git a/awx/ui/client/src/activity-stream/factories/build-anchor.factory.js b/awx/ui/client/src/activity-stream/factories/build-anchor.factory.js new file mode 100644 index 0000000000..80ba348b41 --- /dev/null +++ b/awx/ui/client/src/activity-stream/factories/build-anchor.factory.js @@ -0,0 +1,78 @@ +export default + function BuildAnchor($log, $filter) { + // Returns a full resource_name HTML string if link can be derived from supplied context + // returns name of resource if activity stream object doesn't contain enough data to build a UI url + // arguments are: a summary_field object, a resource type, an activity stream object + return function (obj, resource, activity) { + var url = '/#/'; + // try/except pattern asserts that: + // if we encounter a case where a UI url can't or shouldn't be generated, just supply the name of the resource + try { + // catch-all case to avoid generating urls if a resource has been deleted + // if a resource still exists, it'll be serialized in the activity's summary_fields + if (!activity.summary_fields[resource]){ + throw {name : 'ResourceDeleted', message: 'The referenced resource no longer exists'}; + } + switch (resource) { + case 'custom_inventory_script': + url += 'inventory_scripts/' + obj.id + '/'; + break; + case 'group': + if (activity.operation === 'create' || activity.operation === 'delete'){ + // the API formats the changes.inventory field as str 'myInventoryName-PrimaryKey' + var inventory_id = _.last(activity.changes.inventory.split('-')); + url += 'inventories/' + inventory_id + '/manage?group=' + activity.changes.id; + } + else { + url += 'inventories/' + activity.summary_fields.inventory[0].id + '/manage?group=' + (activity.changes.id || activity.changes.object1_pk); + } + break; + case 'host': + url += 'home/hosts/' + obj.id; + break; + case 'job': + url += 'jobs/' + obj.id; + break; + case 'inventory': + url += 'inventories/' + obj.id + '/'; + break; + case 'schedule': + // schedule urls depend on the resource they're associated with + if (activity.summary_fields.job_template){ + url += 'job_templates/' + activity.summary_fields.job_template.id + '/schedules/' + obj.id; + } + else if (activity.summary_fields.project){ + url += 'projects/' + activity.summary_fields.project.id + '/schedules/' + obj.id; + } + else if (activity.summary_fields.system_job_template){ + url += 'management_jobs/' + activity.summary_fields.system_job_template.id + '/schedules/edit/' + obj.id; + } + // urls for inventory sync schedules currently depend on having an inventory id and group id + else { + throw {name : 'NotImplementedError', message : 'activity.summary_fields to build this url not implemented yet'}; + } + break; + case 'notification_template': + url += `notification_templates/${obj.id}`; + break; + case 'role': + throw {name : 'NotImplementedError', message : 'role object management is not consolidated to a single UI view'}; + case 'job_template': + url += `templates/job_template/${obj.id}`; + break; + case 'workflow_job_template': + url += `templates/workflow_job_template/${obj.id}`; + break; + default: + url += resource + 's/' + obj.id + '/'; + } + return ' ' + $filter('sanitize')(obj.name || obj.username) + ' '; + } + catch(err){ + $log.debug(err); + return ' ' + $filter('sanitize')(obj.name || obj.username || '') + ' '; + } + }; + } + + BuildAnchor.$inject = ['$log', '$filter']; diff --git a/awx/ui/client/src/activity-stream/factories/build-description.factory.js b/awx/ui/client/src/activity-stream/factories/build-description.factory.js new file mode 100644 index 0000000000..ecd596dda2 --- /dev/null +++ b/awx/ui/client/src/activity-stream/factories/build-description.factory.js @@ -0,0 +1,126 @@ +export default + function BuildDescription(BuildAnchor, $log, i18n) { + return function (activity) { + + var pastTense = function(operation){ + return (/e$/.test(activity.operation)) ? operation + 'd ' : operation + 'ed '; + }; + // convenience method to see if dis+association operation involves 2 groups + // the group cases are slightly different because groups can be dis+associated into each other + var isGroupRelationship = function(activity){ + return activity.object1 === 'group' && activity.object2 === 'group' && activity.summary_fields.group.length > 1; + }; + + // Activity stream objects will outlive the resources they reference + // in that case, summary_fields will not be available - show generic error text instead + try { + activity.description = pastTense(activity.operation); + switch(activity.object_association){ + // explicit role dis+associations + case 'role': + // object1 field is resource targeted by the dis+association + // object2 field is the resource the role is inherited from + // summary_field.role[0] contains ref info about the role + switch(activity.operation){ + // expected outcome: "disassociated role_name from " + case 'disassociate': + if (isGroupRelationship(activity)){ + activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field + + ' from ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); + } + else{ + activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + + ' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + } + break; + // expected outcome: "associated role_name to " + case 'associate': + if (isGroupRelationship(activity)){ + activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field + + ' to ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); + } + else{ + activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + + ' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + } + break; + } + break; + // inherited role dis+associations (logic identical to case 'role') + case 'parents': + // object1 field is resource targeted by the dis+association + // object2 field is the resource the role is inherited from + // summary_field.role[0] contains ref info about the role + switch(activity.operation){ + // expected outcome: "disassociated role_name from " + case 'disassociate': + if (isGroupRelationship(activity)){ + activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + + 'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); + } + else{ + activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + + ' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + } + break; + // expected outcome: "associated role_name to " + case 'associate': + if (isGroupRelationship(activity)){ + activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) + + 'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity); + } + else{ + activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + + ' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + } + break; + } + break; + // CRUD operations / resource on resource dis+associations + default: + switch(activity.operation){ + // expected outcome: "disassociated from " + case 'disassociate' : + if (isGroupRelationship(activity)){ + activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + + 'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); + } + else { + activity.description += activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + + 'from ' + activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + } + break; + // expected outcome "associated to " + case 'associate': + // groups are the only resource that can be associated/disassociated into each other + if (isGroupRelationship(activity)){ + activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) + + 'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity); + } + else { + activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity) + + 'to ' + activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity); + } + break; + case 'delete': + activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity); + break; + // expected outcome: "operation " + case 'update': + activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + break; + case 'create': + activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity); + break; + } + break; + } + } + catch(err){ + $log.debug(err); + activity.description = i18n._('Event summary not available'); + } + }; + } + + BuildDescription.$inject = ['BuildAnchor', '$log', 'i18n']; diff --git a/awx/ui/client/src/activity-stream/factories/show-detail.factory.js b/awx/ui/client/src/activity-stream/factories/show-detail.factory.js new file mode 100644 index 0000000000..fde935e108 --- /dev/null +++ b/awx/ui/client/src/activity-stream/factories/show-detail.factory.js @@ -0,0 +1,39 @@ +export default + function ShowDetail($filter, $rootScope, Rest, Alert, GenerateForm, ProcessErrors, GetBasePath, FormatDate, ActivityDetailForm, Empty, Find) { + return function (params, scope) { + + var activity_id = params.activity_id, + activity = Find({ list: params.scope.activities, key: 'id', val: activity_id }), + element; + + if (activity) { + + // Grab our element out of the dom + element = angular.element(document.getElementById('stream-detail-modal')); + + // Grab the modal's scope so that we can set a few variables + scope = element.scope(); + + scope.changes = activity.changes; + scope.user = ((activity.summary_fields.actor) ? activity.summary_fields.actor.username : 'system') + + ' on ' + $filter('longDate')(activity.timestamp); + scope.operation = activity.description; + scope.header = "Event " + activity.id; + + // Open the modal + $('#stream-detail-modal').modal({ + show: true, + backdrop: 'static', + keyboard: true + }); + + if (!scope.$$phase) { + scope.$digest(); + } + } + + }; + } + + ShowDetail.$inject = ['$filter', '$rootScope', 'Rest', 'Alert', 'GenerateForm', 'ProcessErrors', 'GetBasePath', 'FormatDate', + 'ActivityDetailForm', 'Empty', 'Find']; diff --git a/awx/ui/client/src/activity-stream/factories/stream.factory.js b/awx/ui/client/src/activity-stream/factories/stream.factory.js new file mode 100644 index 0000000000..b3725fc2aa --- /dev/null +++ b/awx/ui/client/src/activity-stream/factories/stream.factory.js @@ -0,0 +1,54 @@ +export default + function Stream($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors, + Wait, StreamList, GenerateList, FormatDate, + BuildDescription, ShowDetail) { + return function (params) { + + var scope = params.scope; + + $rootScope.flashMessage = null; + + // descriptive title describing what AS is showing + scope.streamTitle = (params && params.title) ? params.title : null; + + scope.refreshStream = function () { + $state.go('.', null, {reload: true}); + }; + + scope.showDetail = function (id) { + ShowDetail({ + scope: scope, + activity_id: id + }); + }; + + if(scope.activities && scope.activities.length > 0) { + buildUserAndDescription(); + } + + scope.$watch('activities', function(){ + // Watch for future update to scope.activities (like page change, column sort, search, etc) + buildUserAndDescription(); + }); + + function buildUserAndDescription(){ + scope.activities.forEach(function(activity, i) { + // build activity.user + if (scope.activities[i].summary_fields.actor) { + scope.activities[i].user = "" + + scope.activities[i].summary_fields.actor.username + ""; + } else { + scope.activities[i].user = 'system'; + } + // build description column / action text + BuildDescription(scope.activities[i]); + + }); + } + + }; + } + + Stream.$inject = ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath', + 'ProcessErrors', 'Wait', 'StreamList', 'generateList', 'FormatDate', 'BuildDescription', + 'ShowDetail']; diff --git a/awx/ui/client/src/activity-stream/main.js b/awx/ui/client/src/activity-stream/main.js index 381c421c87..44c82217ab 100644 --- a/awx/ui/client/src/activity-stream/main.js +++ b/awx/ui/client/src/activity-stream/main.js @@ -6,14 +6,20 @@ import activityStreamRoute from './activitystream.route'; import activityStreamController from './activitystream.controller'; - import streamDropdownNav from './streamDropdownNav/stream-dropdown-nav.directive'; - import streamDetailModal from './streamDetailModal/main'; +import BuildAnchor from './factories/build-anchor.factory'; +import BuildDescription from './factories/build-description.factory'; +import ShowDetail from './factories/show-detail.factory'; +import Stream from './factories/stream.factory'; export default angular.module('activityStream', [streamDetailModal.name]) .controller('activityStreamController', activityStreamController) .directive('streamDropdownNav', streamDropdownNav) + .factory('BuildAnchor', BuildAnchor) + .factory('BuildDescription', BuildDescription) + .factory('ShowDetail', ShowDetail) + .factory('Stream', Stream) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState(activityStreamRoute); }]); diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index aade185211..50a55cc76d 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -39,7 +39,6 @@ if ($basePath) { // Modules import './helpers'; import './lists'; -import './widgets'; import './filters'; import portalMode from './portal-mode/main'; import systemTracking from './system-tracking/main'; diff --git a/awx/ui/client/src/widgets.js b/awx/ui/client/src/widgets.js deleted file mode 100644 index dae0b52e0f..0000000000 --- a/awx/ui/client/src/widgets.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import "./widgets/InventorySyncStatus"; -import "./widgets/JobStatus"; -import "./widgets/ObjectCount"; -import "./widgets/SCMSyncStatus"; -import "./widgets/Stream"; \ No newline at end of file diff --git a/awx/ui/client/src/widgets/InventorySyncStatus.js b/awx/ui/client/src/widgets/InventorySyncStatus.js deleted file mode 100644 index 75438f03de..0000000000 --- a/awx/ui/client/src/widgets/InventorySyncStatus.js +++ /dev/null @@ -1,115 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name widgets.function:InventorySyncStatus - * @description - * InventorySyncStatus.js - * - * Dashboard widget showing object counts and license availability. - * - */ - - - -angular.module('InventorySyncStatusWidget', ['RestServices', 'Utilities']) - .factory('InventorySyncStatus', ['$rootScope', '$compile', function ($rootScope, $compile) { - return function (params) { - - var scope = params.scope, - target = params.target, - dashboard = params.dashboard, - html, group_total, group_fail, element, src; - - html = "
\n"; - html += "
Inventory Sync Status
\n"; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - - function makeRow(params) { - var label = params.label, - count = params.count, - fail = params.fail, - link = params.link, - fail_link = params.fail_link, - html = "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - return html; - } - - html += makeRow({ - label: 'Inventories', - count: (dashboard.inventories && dashboard.inventories.total_with_inventory_source) ? - dashboard.inventories.total_with_inventory_source : 0, - fail: (dashboard.inventories && dashboard.inventories.inventory_failed) ? dashboard.inventories.inventory_failed : 0, - link: '/#/inventories/?has_inventory_sources=true', - fail_link: '/#/inventories/?inventory_sources_with_failures=true' - }); - - group_total = 0; - group_fail = 0; - if (dashboard.inventory_sources) { - for (src in dashboard.inventory_sources) { - group_total += (dashboard.inventory_sources[src].total) ? dashboard.inventory_sources[src].total : 0; - group_fail += (dashboard.inventory_sources[src].failed) ? dashboard.inventory_sources[src].failed : 0; - } - } - - html += makeRow({ - label: 'Groups', - count: group_total, - fail: group_fail, - link: '/#/home/groups/?has_external_source=true', - fail_link: '/#/home/groups/?status=failed' - }); - - // Each inventory source - for (src in dashboard.inventory_sources) { - if (dashboard.inventory_sources[src].total) { - html += makeRow({ - label: dashboard.inventory_sources[src].label, - count: (dashboard.inventory_sources[src].total) ? dashboard.inventory_sources[src].total : 0, - fail: (dashboard.inventory_sources[src].failed) ? dashboard.inventory_sources[src].failed : 0, - link: '/#/home/groups/?source=' + src, - fail_link: '/#/home/groups/?status=failed&source=' + src - }); - } - } - - html += "\n"; - html += "
FailedTotal
0) ? 'failed-column' : 'zero-column'; - html += " text-right\">"; - html += "" + fail + ""; - html += ""; - html += "" + count + ""; - html += "
\n"; - html += "
\n"; - html += "
\n"; - html += "
\n"; - - element = angular.element(document.getElementById(target)); - element.html(html); - $compile(element)(scope); - scope.$emit('WidgetLoaded'); - - }; - } -]); diff --git a/awx/ui/client/src/widgets/JobStatus.js b/awx/ui/client/src/widgets/JobStatus.js deleted file mode 100644 index 2ec89936a3..0000000000 --- a/awx/ui/client/src/widgets/JobStatus.js +++ /dev/null @@ -1,107 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name widgets.function:JobStatus - * @description -* JobStatus.js -* -* Dashboard widget showing object counts and license availability. -* -*/ - - - -angular.module('JobStatusWidget', ['RestServices', 'Utilities']) - .factory('JobStatus', ['$rootScope', '$compile', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', - function ($rootScope, $compile) { - return function (params) { - - var scope = params.scope, - target = params.target, - dashboard = params.dashboard, - html = '', element; - - html = "
\n"; - html += "
Job Status
\n"; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - - function makeRow(params) { - var html = '', - label = params.label, - link = params.link, - fail_link = params.fail_link, - count = params.count, - fail = params.fail; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - return html; - } - - html += makeRow({ - label: 'Jobs', - link: '/#/jobs', - count: (dashboard.jobs && dashboard.jobs.total) ? dashboard.jobs.total : 0, - fail: (dashboard.jobs && dashboard.jobs.failed) ? dashboard.jobs.failed : 0, - fail_link: '/#/jobs/?status=failed' - }); - html += makeRow({ - label: 'Inventories', - link: '/#/inventories', - count: (dashboard.inventories && dashboard.inventories.total) ? dashboard.inventories.total : 0, - fail: (dashboard.inventories && dashboard.inventories.job_failed) ? dashboard.inventories.job_failed : 0, - fail_link: '/#/inventories/?has_active_failures=true' - }); - html += makeRow({ - label: 'Groups', - link: '/#/home/groups', - count: (dashboard.groups && dashboard.groups.total) ? dashboard.groups.total : 0, - fail: (dashboard.groups && dashboard.groups.job_failed) ? dashboard.groups.job_failed : 0, - fail_link: '/#/home/groups/?has_active_failures=true' - }); - html += makeRow({ - label: 'Hosts', - link: '/#/home/hosts', - count: (dashboard.hosts && dashboard.hosts.total) ? dashboard.hosts.total : 0, - fail: (dashboard.hosts && dashboard.hosts.failed) ? dashboard.hosts.failed : 0, - fail_link: '/#/home/hosts/?has_active_failures=true' - }); - - html += "\n"; - html += "
FailedTotal
0) ? 'failed-column' : 'zero-column'; - html += " text-right\">"; - html += "" + fail + ""; - html += ""; - html += "" + count + ""; - html += "
\n"; - html += "
\n"; - html += "
\n"; - html += "
\n"; - - element = angular.element(document.getElementById(target)); - element.html(html); - $compile(element)(scope); - scope.$emit('WidgetLoaded'); - - }; - } - ]); diff --git a/awx/ui/client/src/widgets/ObjectCount.js b/awx/ui/client/src/widgets/ObjectCount.js deleted file mode 100644 index 84ddd6fb80..0000000000 --- a/awx/ui/client/src/widgets/ObjectCount.js +++ /dev/null @@ -1,78 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name widgets.function:ObjectCount - * @description - * ObjectCount.js - * - * Dashboard widget showing object counts and license availability. - * - */ - - - -angular.module('ObjectCountWidget', ['RestServices', 'Utilities']) - .factory('ObjectCount', ['$rootScope', '$compile', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', - function ($rootScope, $compile) { - return function (params) { - - var scope = params.scope, - target = params.target, - dashboard = params.dashboard, - keys = ['organizations', 'users', 'teams', 'credentials', 'projects', 'inventories', 'groups', 'hosts', - 'job_templates', 'jobs' - ], - i, html, element; - - html = "
\n"; - html += "
System Summary
\n"; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - - function makeRow(params) { - var html = '', - label = params.label, - link = params.link, - count = params.count; - html += "\n"; - html += "\n"; - html += "\n"; - return html; - } - - for (i = 0; i < keys.length; i++) { - html += makeRow({ - label: keys[i], - link: '/#/' + ((keys[i] === 'hosts' || keys[i] === 'groups') ? 'home/' + keys[i] : keys[i]), - count: (dashboard[keys[i]] && dashboard[keys[i]].total) ? dashboard[keys[i]].total : 0 - }); - } - - html += "\n"; - html += "
Total
"; - html += "" + count + ""; - html += "
\n"; - html += "
\n"; - html += "
\n"; - element = angular.element(document.getElementById(target)); - element.html(html); - $compile(element)(scope); - scope.$emit('WidgetLoaded'); - }; - } - ]); diff --git a/awx/ui/client/src/widgets/SCMSyncStatus.js b/awx/ui/client/src/widgets/SCMSyncStatus.js deleted file mode 100644 index b4d452f06a..0000000000 --- a/awx/ui/client/src/widgets/SCMSyncStatus.js +++ /dev/null @@ -1,108 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name widgets.function:SCMSyncStatus - * @description - * SCMSyncStatus.js - * - * Dashboard widget showing object counts and license availability. - * - */ - - -angular.module('SCMSyncStatusWidget', ['RestServices', 'Utilities']) - .factory('SCMSyncStatus', ['$rootScope', '$compile', - function ($rootScope, $compile) { - return function (params) { - - var scope = params.scope, - target = params.target, - dashboard = params.dashboard, - i, html, total_count, element, type, labelList; - - html = "
\n"; - html += "
Project SCM Status
\n"; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - - function makeRow(params) { - var html = '', - label = params.label, - link = params.link, - fail_link = params.fail_link, - count = params.count, - fail = params.fail; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - return html; - } - - total_count = 0; - if (dashboard.scm_types) { - for (type in dashboard.scm_types) { - total_count += (dashboard.scm_types[type].total) ? dashboard.scm_types[type].total : 0; - } - } - - html += makeRow({ - label: 'Projects', - link: '/#/projects', - count: total_count, - fail: (dashboard.projects && dashboard.projects.failed) ? dashboard.projects.failed : 0, - fail_link: '/#/projects/?status=failed' - }); - - labelList = []; - for (type in dashboard.scm_types) { - labelList.push(type); - } - labelList.sort(); - for (i = 0; i < labelList.length; i++) { - type = labelList[i]; - if (dashboard.scm_types[type].total) { - html += makeRow({ - label: dashboard.scm_types[type].label, - link: '/#/projects/?scm_type=' + type, - count: (dashboard.scm_types[type].total) ? dashboard.scm_types[type].total : 0, - fail: (dashboard.scm_types[type].failed) ? dashboard.scm_types[type].failed : 0, - fail_link: '/#/projects/?scm_type=' + type + '&status=failed' - }); - } - } - - html += "\n"; - html += "
FailedTotal
" + label + " 0) ? 'failed-column' : 'zero-column'; - html += " text-right\">"; - html += "" + fail + ""; - html += ""; - html += "" + count + ""; - html += "
\n"; - html += "
\n"; - html += "
\n"; - html += "
\n"; - - element = angular.element(document.getElementById(target)); - element.html(html); - $compile(element)(scope); - scope.$emit('WidgetLoaded'); - - }; - } - ]); diff --git a/awx/ui/client/src/widgets/Stream.js b/awx/ui/client/src/widgets/Stream.js deleted file mode 100644 index 64315e7953..0000000000 --- a/awx/ui/client/src/widgets/Stream.js +++ /dev/null @@ -1,317 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name widgets.function:Stream - * @description - * Stream.js - * - * Activity stream widget that can be called from anywhere - * - */ - -import listGenerator from '../shared/list-generator/main'; - - -angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefinition', listGenerator.name, 'StreamWidget', -]) - -.factory('BuildAnchor', [ '$log', '$filter', - // Returns a full resource_name HTML string if link can be derived from supplied context - // returns name of resource if activity stream object doesn't contain enough data to build a UI url - // arguments are: a summary_field object, a resource type, an activity stream object - function ($log, $filter) { - return function (obj, resource, activity) { - var url = '/#/'; - // try/except pattern asserts that: - // if we encounter a case where a UI url can't or shouldn't be generated, just supply the name of the resource - try { - // catch-all case to avoid generating urls if a resource has been deleted - // if a resource still exists, it'll be serialized in the activity's summary_fields - if (!activity.summary_fields[resource]){ - throw {name : 'ResourceDeleted', message: 'The referenced resource no longer exists'}; - } - switch (resource) { - case 'custom_inventory_script': - url += 'inventory_scripts/' + obj.id + '/'; - break; - case 'group': - if (activity.operation === 'create' || activity.operation === 'delete'){ - // the API formats the changes.inventory field as str 'myInventoryName-PrimaryKey' - var inventory_id = _.last(activity.changes.inventory.split('-')); - url += 'inventories/' + inventory_id + '/manage?group=' + activity.changes.id; - } - else { - url += 'inventories/' + activity.summary_fields.inventory[0].id + '/manage?group=' + (activity.changes.id || activity.changes.object1_pk); - } - break; - case 'host': - url += 'home/hosts/' + obj.id; - break; - case 'job': - url += 'jobs/' + obj.id; - break; - case 'inventory': - url += 'inventories/' + obj.id + '/'; - break; - case 'schedule': - // schedule urls depend on the resource they're associated with - if (activity.summary_fields.job_template){ - url += 'job_templates/' + activity.summary_fields.job_template.id + '/schedules/' + obj.id; - } - else if (activity.summary_fields.project){ - url += 'projects/' + activity.summary_fields.project.id + '/schedules/' + obj.id; - } - else if (activity.summary_fields.system_job_template){ - url += 'management_jobs/' + activity.summary_fields.system_job_template.id + '/schedules/edit/' + obj.id; - } - // urls for inventory sync schedules currently depend on having an inventory id and group id - else { - throw {name : 'NotImplementedError', message : 'activity.summary_fields to build this url not implemented yet'}; - } - break; - case 'notification_template': - url += `notification_templates/${obj.id}`; - break; - case 'role': - throw {name : 'NotImplementedError', message : 'role object management is not consolidated to a single UI view'}; - case 'job_template': - url += `templates/job_template/${obj.id}`; - break; - case 'workflow_job_template': - url += `templates/workflow_job_template/${obj.id}`; - break; - default: - url += resource + 's/' + obj.id + '/'; - } - return ' ' + $filter('sanitize')(obj.name || obj.username) + ' '; - } - catch(err){ - $log.debug(err); - return ' ' + $filter('sanitize')(obj.name || obj.username || '') + ' '; - } - }; - } -]) - -.factory('BuildDescription', ['BuildAnchor', '$log', 'i18n', - function (BuildAnchor, $log, i18n) { - return function (activity) { - - var pastTense = function(operation){ - return (/e$/.test(activity.operation)) ? operation + 'd ' : operation + 'ed '; - }; - // convenience method to see if dis+association operation involves 2 groups - // the group cases are slightly different because groups can be dis+associated into each other - var isGroupRelationship = function(activity){ - return activity.object1 === 'group' && activity.object2 === 'group' && activity.summary_fields.group.length > 1; - }; - - // Activity stream objects will outlive the resources they reference - // in that case, summary_fields will not be available - show generic error text instead - try { - activity.description = pastTense(activity.operation); - switch(activity.object_association){ - // explicit role dis+associations - case 'role': - // object1 field is resource targeted by the dis+association - // object2 field is the resource the role is inherited from - // summary_field.role[0] contains ref info about the role - switch(activity.operation){ - // expected outcome: "disassociated role_name from " - case 'disassociate': - if (isGroupRelationship(activity)){ - activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' from ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); - } - else{ - activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); - } - break; - // expected outcome: "associated role_name to " - case 'associate': - if (isGroupRelationship(activity)){ - activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' to ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); - } - else{ - activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); - } - break; - } - break; - // inherited role dis+associations (logic identical to case 'role') - case 'parents': - // object1 field is resource targeted by the dis+association - // object2 field is the resource the role is inherited from - // summary_field.role[0] contains ref info about the role - switch(activity.operation){ - // expected outcome: "disassociated role_name from " - case 'disassociate': - if (isGroupRelationship(activity)){ - activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + - 'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); - } - else{ - activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); - } - break; - // expected outcome: "associated role_name to " - case 'associate': - if (isGroupRelationship(activity)){ - activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) + - 'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity); - } - else{ - activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); - } - break; - } - break; - // CRUD operations / resource on resource dis+associations - default: - switch(activity.operation){ - // expected outcome: "disassociated from " - case 'disassociate' : - if (isGroupRelationship(activity)){ - activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + - 'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); - } - else { - activity.description += activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + - 'from ' + activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); - } - break; - // expected outcome "associated to " - case 'associate': - // groups are the only resource that can be associated/disassociated into each other - if (isGroupRelationship(activity)){ - activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) + - 'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity); - } - else { - activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity) + - 'to ' + activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity); - } - break; - case 'delete': - activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity); - break; - // expected outcome: "operation " - case 'update': - activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); - break; - case 'create': - activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity); - break; - } - break; - } - } - catch(err){ - $log.debug(err); - activity.description = i18n._('Event summary not available'); - } - }; - } -]) - -.factory('ShowDetail', ['$filter', '$rootScope', 'Rest', 'Alert', 'GenerateForm', 'ProcessErrors', 'GetBasePath', 'FormatDate', - 'ActivityDetailForm', 'Empty', 'Find', - function ($filter, $rootScope, Rest, Alert, GenerateForm, ProcessErrors, GetBasePath, FormatDate, ActivityDetailForm, Empty, Find) { - return function (params, scope) { - - var activity_id = params.activity_id, - activity = Find({ list: params.scope.activities, key: 'id', val: activity_id }), - element; - - if (activity) { - - // Grab our element out of the dom - element = angular.element(document.getElementById('stream-detail-modal')); - - // Grab the modal's scope so that we can set a few variables - scope = element.scope(); - - scope.changes = activity.changes; - scope.user = ((activity.summary_fields.actor) ? activity.summary_fields.actor.username : 'system') + - ' on ' + $filter('longDate')(activity.timestamp); - scope.operation = activity.description; - scope.header = "Event " + activity.id; - - // Open the modal - $('#stream-detail-modal').modal({ - show: true, - backdrop: 'static', - keyboard: true - }); - - if (!scope.$$phase) { - scope.$digest(); - } - } - }; - } -]) - -.factory('Stream', ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath', - 'ProcessErrors', 'Wait', 'StreamList', 'generateList', 'FormatDate', 'BuildDescription', - 'ShowDetail', - function ($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors, - Wait, StreamList, GenerateList, FormatDate, - BuildDescription, ShowDetail) { - return function (params) { - - var scope = params.scope; - - $rootScope.flashMessage = null; - - // descriptive title describing what AS is showing - scope.streamTitle = (params && params.title) ? params.title : null; - - scope.refreshStream = function () { - $state.go('.', null, {reload: true}); - }; - - scope.showDetail = function (id) { - ShowDetail({ - scope: scope, - activity_id: id - }); - }; - - if(scope.activities && scope.activities.length > 0) { - buildUserAndDescription(); - } - - scope.$watch('activities', function(){ - // Watch for future update to scope.activities (like page change, column sort, search, etc) - buildUserAndDescription(); - }); - - function buildUserAndDescription(){ - scope.activities.forEach(function(activity, i) { - // build activity.user - if (scope.activities[i].summary_fields.actor) { - scope.activities[i].user = "" + - scope.activities[i].summary_fields.actor.username + ""; - } else { - scope.activities[i].user = 'system'; - } - // build description column / action text - BuildDescription(scope.activities[i]); - - }); - } - }; - } -]); From ab0e20c9766e276930b93f9b6bde95701b335ede Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 1 Mar 2017 11:54:18 -0500 Subject: [PATCH 12/39] Remove StreamWidget --- awx/ui/client/src/app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 50a55cc76d..bff93c81c3 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -178,7 +178,6 @@ var tower = angular.module('Tower', [ 'md5Helper', 'SelectionHelper', 'HostGroupsFormDefinition', - 'StreamWidget', 'JobsHelper', 'CredentialsHelper', 'StreamListDefinition', From 55b74ddd239f2cfe914c896d4edbf6a97e46e583 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 1 Mar 2017 12:46:39 -0500 Subject: [PATCH 13/39] fix refresh and refreshJobs functions --- awx/ui/client/src/app.js | 8 ++++++++ awx/ui/client/src/management-jobs/card/card.controller.js | 6 ------ .../controllers/organizations-projects.controller.js | 6 ------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index aade185211..a501bf16ee 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -287,6 +287,14 @@ var tower = angular.module('Tower', [ $log.debug(`$state.defaultErrorHandler: ${error}`); }); + $rootScope.refresh = function() { + $state.go('.', null, {reload: true}); + }; + + $rootScope.refreshJobs = function(){ + $state.go('.', null, {reload: true}); + }; + function activateTab() { // Make the correct tab active var base = $location.path().replace(/^\//, '').split('/')[0]; diff --git a/awx/ui/client/src/management-jobs/card/card.controller.js b/awx/ui/client/src/management-jobs/card/card.controller.js index 29fb0da07a..783ddafc92 100644 --- a/awx/ui/client/src/management-jobs/card/card.controller.js +++ b/awx/ui/client/src/management-jobs/card/card.controller.js @@ -265,12 +265,6 @@ export default }); }; - parent_scope.refreshJobs = function(){ - // TODO: what should I do here? - // @issue: OLD SEARCH - // scope.search(SchedulesList.iterator); - }; - var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) { if(toState.name === "managementJobsList") { // We are on the management job list view - nothing needs to be highlighted diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index f2ab1307f2..69acfb1622 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -284,12 +284,6 @@ export default ['$scope', '$rootScope', '$location', '$log', }); }; - $scope.refresh = function() { - // TODO: what should I do here? - // @issue: OLD SEARCH - // $scope.search(list.iterator); - }; - $scope.SCMUpdate = function(project_id, event) { try { $(event.target).tooltip('hide'); From 45620f35a3f33ad36c2bccbb66f72458a828015e Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 1 Mar 2017 13:50:02 -0500 Subject: [PATCH 14/39] fix jshint error --- awx/ui/client/src/management-jobs/card/card.controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/awx/ui/client/src/management-jobs/card/card.controller.js b/awx/ui/client/src/management-jobs/card/card.controller.js index 783ddafc92..6bdb32bb0c 100644 --- a/awx/ui/client/src/management-jobs/card/card.controller.js +++ b/awx/ui/client/src/management-jobs/card/card.controller.js @@ -31,8 +31,7 @@ export default }); }; getManagementJobs(); - var scope = $rootScope.$new(), - parent_scope = scope; + var scope = $rootScope.$new(); scope.cleanupJob = true; // This handles the case where the user refreshes the management job notifications page. From 5c22cb1f698282ae79d0d2090d345fc35f31bfeb Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 29 Nov 2016 17:39:56 -0800 Subject: [PATCH 15/39] moving some functions from JobDetailService to jobResultsService in order to decouple the old job details from the new job results so we can remove the job detail files at some point --- .../host-event/host-event.controller.js | 6 +- .../host-event/host-event.route.js | 8 +- .../src/job-results/job-results.service.js | 98 ++++++++++++++++++- 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/awx/ui/client/src/job-results/host-event/host-event.controller.js b/awx/ui/client/src/job-results/host-event/host-event.controller.js index 6015dd3a63..e6c5fc71c7 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.controller.js +++ b/awx/ui/client/src/job-results/host-event/host-event.controller.js @@ -6,10 +6,10 @@ export default - ['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'hostEvent', 'hostResults', - function($stateParams, $scope, $state, Wait, JobDetailService, hostEvent, hostResults){ + ['$stateParams', '$scope', '$state', 'Wait', 'jobResultsService', 'hostEvent', 'hostResults', + function($stateParams, $scope, $state, Wait, jobResultsService, hostEvent, hostResults){ - $scope.processEventStatus = JobDetailService.processEventStatus; + $scope.processEventStatus = jobResultsService.processEventStatus; $scope.hostResults = []; // Avoid rendering objects in the details fieldset // ng-if="processResults(value)" via host-event-details.partial.html diff --git a/awx/ui/client/src/job-results/host-event/host-event.route.js b/awx/ui/client/src/job-results/host-event/host-event.route.js index 23d5fe2451..eb796fa7df 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.route.js +++ b/awx/ui/client/src/job-results/host-event/host-event.route.js @@ -13,14 +13,14 @@ var hostEventModal = { templateUrl: templateUrl('job-results/host-event/host-event-modal'), 'abstract': false, resolve: { - hostEvent: ['JobDetailService', '$stateParams', function(JobDetailService, $stateParams) { - return JobDetailService.getRelatedJobEvents($stateParams.id, { + hostEvent: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) { + return jobResultsService.getRelatedJobEvents($stateParams.id, { id: $stateParams.eventId }).then(function(res) { return res.data.results[0]; }); }], - hostResults: ['JobDetailService', '$stateParams', function(JobDetailService, $stateParams) { - return JobDetailService.getJobEventChildren($stateParams.taskId).then(res => res.data.results); + hostResults: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) { + return jobResultsService.getJobEventChildren($stateParams.taskId).then(res => res.data.results); }] }, onExit: function() { diff --git a/awx/ui/client/src/job-results/job-results.service.js b/awx/ui/client/src/job-results/job-results.service.js index cb51dc8864..3817a922b8 100644 --- a/awx/ui/client/src/job-results/job-results.service.js +++ b/awx/ui/client/src/job-results/job-results.service.js @@ -5,8 +5,8 @@ *************************************************/ -export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', 'GetBasePath', 'Alert', -function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, GetBasePath, Alert) { +export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', 'GetBasePath', 'Alert', '$rootScope', +function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, GetBasePath, Alert, $rootScope) { var val = { // the playbook_on_stats event returns the count data in a weird format. // format to what we need! @@ -190,6 +190,100 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo }); return val.promise; + }, + // Generate a helper class for job_event statuses + // the stack for which status to display is + // unreachable > failed > changed > ok + // uses the API's runner events and convenience properties .failed .changed to determine status. + // see: job_event_callback.py for more filters to support + processEventStatus: function(event){ + if (event.event === 'runner_on_unreachable'){ + return { + class: 'HostEvents-status--unreachable', + status: 'unreachable' + }; + } + // equiv to 'runner_on_error' && 'runner on failed' + if (event.failed){ + return { + class: 'HostEvents-status--failed', + status: 'failed' + }; + } + // catch the changed case before ok, because both can be true + if (event.changed){ + return { + class: 'HostEvents-status--changed', + status: 'changed' + }; + } + if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok'){ + return { + class: 'HostEvents-status--ok', + status: 'ok' + }; + } + if (event.event === 'runner_on_skipped'){ + return { + class: 'HostEvents-status--skipped', + status: 'skipped' + }; + } + }, + // GET events related to a job run + // e.g. + // ?event=playbook_on_stats + // ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter + getRelatedJobEvents: function(id, params){ + var url = GetBasePath('jobs'); + url = url + id + '/job_events/?' + this.stringifyParams(params); + Rest.setUrl(url); + return Rest.get() + .success(function(data){ + return data; + }) + .error(function(data, status) { + ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + }, + getJobEventChildren: function(id){ + var url = GetBasePath('job_events'); + url = url + id + '/children/?order_by=host_name'; + Rest.setUrl(url); + return Rest.get() + .success(function(data){ + return data; + }) + .error(function(data, status) { + ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + }, + stringifyParams: function(params){ + return _.reduce(params, (result, value, key) => { + return result + key + '=' + value + '&'; + }, ''); + }, + // the the API passes through Ansible's event_data response + // we need to massage away the verbose & redundant stdout/stderr properties + processJson: function(data){ + // configure fields to ignore + var ignored = [ + 'type', + 'event_data', + 'related', + 'summary_fields', + 'url', + 'ansible_facts', + ]; + // remove ignored properties + var result = _.chain(data).cloneDeep().forEach(function(value, key, collection){ + if (ignored.indexOf(key) > -1){ + delete collection[key]; + } + }).value(); + return result; } }; return val; From edaf3e1f2555e15c973141698e751b6e26e263b9 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 28 Feb 2017 11:54:09 -0800 Subject: [PATCH 16/39] removing old job-details folder --- .../host-event-codemirror.partial.html | 2 - .../host-event-details.partial.html | 45 -- .../host-event/host-event-modal.partial.html | 36 -- .../host-event/host-event-timing.partial.html | 1 - .../host-event/host-event.block.less | 150 ------ .../host-event/host-event.controller.js | 110 ----- .../job-detail/host-event/host-event.route.js | 65 --- .../client/src/job-detail/host-event/main.js | 21 - .../host-events/host-events.route.js | 32 -- .../client/src/job-detail/host-events/main.js | 15 - .../host-summary/host-summary.block.less | 17 - .../host-summary/host-summary.partial.html | 72 --- .../host-summary/host-summary.route.js | 21 - .../src/job-detail/host-summary/main.js | 15 - .../src/job-detail/job-detail.block.less | 240 ---------- .../src/job-detail/job-detail.partial.html | 435 ------------------ .../client/src/job-detail/job-detail.route.js | 91 ---- .../src/job-detail/job-detail.service.js | 215 --------- awx/ui/client/src/job-detail/main.js | 24 - 19 files changed, 1607 deletions(-) delete mode 100644 awx/ui/client/src/job-detail/host-event/host-event-codemirror.partial.html delete mode 100644 awx/ui/client/src/job-detail/host-event/host-event-details.partial.html delete mode 100644 awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html delete mode 100644 awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html delete mode 100644 awx/ui/client/src/job-detail/host-event/host-event.block.less delete mode 100644 awx/ui/client/src/job-detail/host-event/host-event.controller.js delete mode 100644 awx/ui/client/src/job-detail/host-event/host-event.route.js delete mode 100644 awx/ui/client/src/job-detail/host-event/main.js delete mode 100644 awx/ui/client/src/job-detail/host-events/host-events.route.js delete mode 100644 awx/ui/client/src/job-detail/host-events/main.js delete mode 100644 awx/ui/client/src/job-detail/host-summary/host-summary.block.less delete mode 100644 awx/ui/client/src/job-detail/host-summary/host-summary.partial.html delete mode 100644 awx/ui/client/src/job-detail/host-summary/host-summary.route.js delete mode 100644 awx/ui/client/src/job-detail/host-summary/main.js delete mode 100644 awx/ui/client/src/job-detail/job-detail.block.less delete mode 100644 awx/ui/client/src/job-detail/job-detail.partial.html delete mode 100644 awx/ui/client/src/job-detail/job-detail.route.js delete mode 100644 awx/ui/client/src/job-detail/job-detail.service.js delete mode 100644 awx/ui/client/src/job-detail/main.js diff --git a/awx/ui/client/src/job-detail/host-event/host-event-codemirror.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-codemirror.partial.html deleted file mode 100644 index 7c744d2169..0000000000 --- a/awx/ui/client/src/job-detail/host-event/host-event-codemirror.partial.html +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html deleted file mode 100644 index c287788f19..0000000000 --- a/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html +++ /dev/null @@ -1,45 +0,0 @@ -
-
EVENT
- -
- STATUS - - - - - {{processEventStatus(event).status || "No result found"}} - -
-
- ID - {{event.id || "No result found"}} -
-
- CREATED - {{(event.created | longDate) || "No result found"}} -
-
- PLAY - {{event.play || "No result found"}} -
-
- TASK - {{event.task || "No result found"}} -
-
- MODULE - {{event.event_data.res.invocation.module_name || "No result found"}} -
-
-
-
RESULTS
- -
- {{key}} - {{value}} -
-
diff --git a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html deleted file mode 100644 index db236894e8..0000000000 --- a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html +++ /dev/null @@ -1,36 +0,0 @@ - diff --git a/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html deleted file mode 100644 index 06171bd1c5..0000000000 --- a/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html +++ /dev/null @@ -1 +0,0 @@ -
timing
\ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event.block.less b/awx/ui/client/src/job-detail/host-event/host-event.block.less deleted file mode 100644 index 9b31b74e87..0000000000 --- a/awx/ui/client/src/job-detail/host-event/host-event.block.less +++ /dev/null @@ -1,150 +0,0 @@ -// @import "./client/src/shared/branding/colors.less"; -// @import "./client/src/shared/branding/colors.default.less"; -// @import "./client/src/shared/layouts/one-plus-two.less"; -// -// .noselect { -// -webkit-touch-callout: none; /* iOS Safari */ -// -webkit-user-select: none; /* Chrome/Safari/Opera */ -// -khtml-user-select: none; /* Konqueror */ -// -moz-user-select: none; /* Firefox */ -// -ms-user-select: none; /* Internet Explorer/Edge */ -// user-select: none; /* Non-prefixed version, currently -// not supported by any browser */ -// } -// -// @media screen and (min-width: 768px){ -// .HostEvent .modal-dialog{ -// width: 700px; -// } -// } -// .HostEvent .CodeMirror{ -// overflow-x: hidden; -// } -// .HostEvent-controls button.HostEvent-close{ -// color: #FFFFFF; -// text-transform: uppercase; -// padding-left: 15px; -// padding-right: 15px; -// background-color: @default-link; -// border-color: @default-link; -// &:hover{ -// background-color: @default-link-hov; -// border-color: @default-link-hov; -// } -// } -// .HostEvent-body{ -// margin-bottom: 10px; -// } -// .HostEvent-tab { -// color: @btn-txt; -// background-color: @btn-bg; -// font-size: 12px; -// border: 1px solid @btn-bord; -// height: 30px; -// border-radius: 5px; -// margin-right: 20px; -// padding-left: 10px; -// padding-right: 10px; -// padding-bottom: 5px; -// padding-top: 5px; -// transition: background-color 0.2s; -// text-transform: uppercase; -// text-align: center; -// white-space: nowrap; -// .noselect; -// } -// .HostEvent-tab:hover { -// color: @btn-txt; -// background-color: @btn-bg-hov; -// cursor: pointer; -// } -// .HostEvent-tab--selected{ -// color: @btn-txt-sel!important; -// background-color: @default-icon!important; -// border-color: @default-icon!important; -// } -// .HostEvent-view--container{ -// width: 100%; -// display: flex; -// flex-direction: row; -// flex-wrap: nowrap; -// justify-content: space-between; -// } -// .HostEvent .modal-footer{ -// border: 0; -// margin-top: 0px; -// padding-top: 5px; -// } -// .HostEvent-controls{ -// float: right; -// button { -// margin-left: 10px; -// } -// } -// .HostEvent-status--ok{ -// color: @green; -// } -// .HostEvent-status--unreachable{ -// color: @unreachable; -// } -// .HostEvent-status--changed{ -// color: @changed; -// } -// .HostEvent-status--failed{ -// color: @default-err; -// } -// .HostEvent-status--skipped{ -// color: @skipped; -// } -// .HostEvent-title{ -// color: @default-interface-txt; -// font-weight: 600; -// margin-bottom: 8px; -// } -// // .HostEvent .modal-body{ -// // max-height: 500px; -// // overflow-y: auto; -// // padding: 20px; -// // } -// .HostEvent-nav{ -// padding-top: 12px; -// padding-bottom: 12px; -// } -// .HostEvent-field{ -// margin-bottom: 8px; -// flex: 0 1 12em; -// } -// .HostEvent-field--label{ -// text-transform: uppercase; -// flex: 0 1 80px; -// max-width: 80px; -// font-size: 12px; -// word-wrap: break-word; -// } -// .HostEvent-field{ -// .OnePlusTwo-left--detailsRow; -// } -// .HostEvent-field--content{ -// word-wrap: break-word; -// max-width: 13em; -// flex: 0 1 13em; -// } -// .HostEvent-details--left, .HostEvent-details--right{ -// flex: 1 1 47%; -// } -// .HostEvent-details--left{ -// margin-right: 40px; -// } -// .HostEvent-details--right{ -// .HostEvent-field--label{ -// flex: 0 1 25em; -// } -// .HostEvent-field--content{ -// max-width: 15em; -// flex: 0 1 15em; -// align-self: flex-end; -// } -// } -// .HostEvent-button:disabled { -// pointer-events: all!important; -// } diff --git a/awx/ui/client/src/job-detail/host-event/host-event.controller.js b/awx/ui/client/src/job-detail/host-event/host-event.controller.js deleted file mode 100644 index f86452b005..0000000000 --- a/awx/ui/client/src/job-detail/host-event/host-event.controller.js +++ /dev/null @@ -1,110 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - export default - ['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'hostEvent', 'hostResults', - function($stateParams, $scope, $state, Wait, JobDetailService, hostEvent, hostResults){ - - $scope.processEventStatus = JobDetailService.processEventStatus; - $scope.hostResults = []; - // Avoid rendering objects in the details fieldset - // ng-if="processResults(value)" via host-event-details.partial.html - $scope.processResults = function(value){ - if (typeof value === 'object'){return false;} - else {return true;} - }; - $scope.isStdOut = function(){ - if ($state.current.name === 'jobDetails.host-event.stdout' || $state.current.name === 'jobDetaisl.histe-event.stderr'){ - return 'StandardOut-preContainer StandardOut-preContent'; - } - }; - /*ignore jslint start*/ - var initCodeMirror = function(el, data, mode){ - var container = document.getElementById(el); - var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line - lineNumbers: true, - mode: mode - }); - editor.setSize("100%", 300); - editor.getDoc().setValue(data); - }; - /*ignore jslint end*/ - $scope.isActiveState = function(name){ - return $state.current.name === name; - }; - - $scope.getActiveHostIndex = function(){ - var result = $scope.hostResults.filter(function( obj ) { - return obj.id === $scope.event.id; - }); - return $scope.hostResults.indexOf(result[0]); - }; - - $scope.showPrev = function(){ - return $scope.getActiveHostIndex() !== 0; - }; - - $scope.showNext = function(){ - return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1]); - }; - - $scope.goNext = function(){ - var index = $scope.getActiveHostIndex() + 1; - var id = $scope.hostResults[index].id; - $state.go('jobDetail.host-event.details', {eventId: id}); - }; - - $scope.goPrev = function(){ - var index = $scope.getActiveHostIndex() - 1; - var id = $scope.hostResults[index].id; - $state.go('jobDetail.host-event.details', {eventId: id}); - }; - - var init = function(){ - $scope.event = _.cloneDeep(hostEvent); - $scope.hostResults = hostResults; - $scope.json = JobDetailService.processJson(hostEvent); - - // grab standard out & standard error if present, and remove from the results displayed in the details panel - if (hostEvent.event_data.res.stdout){ - $scope.stdout = hostEvent.event_data.res.stdout; - delete $scope.event.event_data.res.stdout; - } - if (hostEvent.event_data.res.stderr){ - $scope.stderr = hostEvent.event_data.res.stderr; - delete $scope.event.event_data.res.stderr; - } - // instantiate Codemirror - // try/catch pattern prevents the abstract-state controller from complaining about element being null - if ($state.current.name === 'jobDetail.host-event.json'){ - try{ - initCodeMirror('HostEvent-codemirror', JSON.stringify($scope.json, null, 4), {name: "javascript", json: true}); - } - catch(err){ - // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController - } - } - else if ($state.current.name === 'jobDetail.host-event.stdout'){ - try{ - initCodeMirror('HostEvent-codemirror', $scope.stdout, 'shell'); - } - catch(err){ - // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController - } - } - else if ($state.current.name === 'jobDetail.host-event.stderr'){ - try{ - initCodeMirror('HostEvent-codemirror', $scope.stderr, 'shell'); - } - catch(err){ - // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController - } - } - $('#HostEvent').modal('show'); - }; - init(); - }]; diff --git a/awx/ui/client/src/job-detail/host-event/host-event.route.js b/awx/ui/client/src/job-detail/host-event/host-event.route.js deleted file mode 100644 index 86e499c2b0..0000000000 --- a/awx/ui/client/src/job-detail/host-event/host-event.route.js +++ /dev/null @@ -1,65 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { templateUrl } from '../../shared/template-url/template-url.factory'; - -var hostEventModal = { - name: 'jobDetail.host-event', - url: '/task/:taskId/host-event/:eventId', - controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-modal'), - 'abstract': true, - resolve: { - hostEvent: ['JobDetailService', '$stateParams', function(JobDetailService, $stateParams) { - return JobDetailService.getRelatedJobEvents($stateParams.id, { - id: $stateParams.eventId - }).then(function(res) { - return res.data.results[0]; }); - }], - hostResults: ['JobDetailService', '$stateParams', function(JobDetailService, $stateParams) { - return JobDetailService.getJobEventChildren($stateParams.taskUuid).then(res => res.data.results); - }] - }, - onExit: function() { - // close the modal - // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X" - $('#HostEvent').modal('hide'); - // hacky way to handle user browsing away via URL bar - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } -}; - -var hostEventDetails = { - name: 'jobDetail.host-event.details', - url: '/details', - controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-details'), -}; - -var hostEventJson = { - name: 'jobDetail.host-event.json', - url: '/json', - controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-codemirror') -}; - -var hostEventStdout = { - name: 'jobDetail.host-event.stdout', - url: '/stdout', - controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-codemirror') -}; - -var hostEventStderr = { - name: 'jobDetail.host-event.stderr', - url: '/stderr', - controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-codemirror') -}; - - -export { hostEventDetails, hostEventJson, hostEventModal, hostEventStdout, hostEventStderr }; diff --git a/awx/ui/client/src/job-detail/host-event/main.js b/awx/ui/client/src/job-detail/host-event/main.js deleted file mode 100644 index 4379427ff8..0000000000 --- a/awx/ui/client/src/job-detail/host-event/main.js +++ /dev/null @@ -1,21 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import {hostEventModal, hostEventDetails, - hostEventJson, hostEventStdout, hostEventStderr} from './host-event.route'; - import controller from './host-event.controller'; - - export default - angular.module('jobDetail.hostEvent', []) - .controller('HostEventController', controller) - - .run(['$stateExtender', function($stateExtender){ - $stateExtender.addState(hostEventModal); - $stateExtender.addState(hostEventDetails); - $stateExtender.addState(hostEventJson); - $stateExtender.addState(hostEventStdout); - $stateExtender.addState(hostEventStderr); - }]); diff --git a/awx/ui/client/src/job-detail/host-events/host-events.route.js b/awx/ui/client/src/job-detail/host-events/host-events.route.js deleted file mode 100644 index 835d17b2b9..0000000000 --- a/awx/ui/client/src/job-detail/host-events/host-events.route.js +++ /dev/null @@ -1,32 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../../shared/template-url/template-url.factory'; - -export default { - name: 'jobDetail.host-events', - url: '/host-events/{hostName:any}?:filter', - controller: 'HostEventsController', - params: { - page_size: 10 - }, - templateUrl: templateUrl('job-detail/host-events/host-events'), - onExit: function(){ - // close the modal - // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X" - $('#HostEvents').modal('hide'); - // hacky way to handle user browsing away via URL bar - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - }, - resolve: { - hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) { - return JobDetailService.getRelatedJobEvents($stateParams.id, { - host_name: $stateParams.hostName - }).success(function(res){ return res.results[0];}); - }] - } -}; diff --git a/awx/ui/client/src/job-detail/host-events/main.js b/awx/ui/client/src/job-detail/host-events/main.js deleted file mode 100644 index 766dd92ca4..0000000000 --- a/awx/ui/client/src/job-detail/host-events/main.js +++ /dev/null @@ -1,15 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import route from './host-events.route'; -import controller from './host-events.controller'; - -export default - angular.module('jobDetail.hostEvents', []) - .controller('HostEventsController', controller) - .run(['$stateExtender', function($stateExtender){ - $stateExtender.addState(route); - }]); diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.block.less b/awx/ui/client/src/job-detail/host-summary/host-summary.block.less deleted file mode 100644 index 00af0d30ea..0000000000 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.block.less +++ /dev/null @@ -1,17 +0,0 @@ -@import '../../shared/branding/colors.default.less'; -.HostSummary-graph--successful{ - text-anchor: start !important; -} -.HostSummary-graph--failed{ - text-anchor: end !important; -} -.HostSummary-graph--changed{ - text-anchor: start !important; -} -.HostSummary-loading{ - border: none; -} -.HostSummary-loading{ - padding-left: 0px !important; - color: @default-interface-txt; -} diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.partial.html b/awx/ui/client/src/job-detail/host-summary/host-summary.partial.html deleted file mode 100644 index 5452990e76..0000000000 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.partial.html +++ /dev/null @@ -1,72 +0,0 @@ - -
-
4 Please select a host below to view a summary of all associated tasks.
-
-
-
-
- - - -
-
-
-
-
- - -
-
-
- -
- - - - - - - -
HostsCompleted Tasks
-
- -
- - - - - - - - - - - - - - - - -
- {{ host.host_name }} - - {{ host.ok - host.changed }} - {{ host.changed }} - {{ host.skipped }} - {{ host.dark }} - {{ host.failures }} -
Initiating job run.
Job is running. Summary will be available on completion.
No matching hosts
-
- -
- -
- -
-
Host Status Summary
-
- -
diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js deleted file mode 100644 index a2de70e5d4..0000000000 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js +++ /dev/null @@ -1,21 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../../shared/template-url/template-url.factory'; - -export default { - name: 'jobDetail.host-summary', - url: '/event-summary', - views:{ - 'host-summary': { - controller: 'HostSummaryController', - templateUrl: templateUrl('job-detail/host-summary/host-summary'), - } - }, - ncyBreadcrumb: { - skip: true // Never display this state in breadcrumb. - } -}; diff --git a/awx/ui/client/src/job-detail/host-summary/main.js b/awx/ui/client/src/job-detail/host-summary/main.js deleted file mode 100644 index fad85ffaf3..0000000000 --- a/awx/ui/client/src/job-detail/host-summary/main.js +++ /dev/null @@ -1,15 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import route from './host-summary.route'; -import controller from './host-summary.controller'; - -export default - angular.module('jobDetail.hostSummary', []) - .controller('HostSummaryController', controller) - .run(['$stateExtender', function($stateExtender){ - $stateExtender.addState(route); - }]); \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/job-detail.block.less b/awx/ui/client/src/job-detail/job-detail.block.less deleted file mode 100644 index c23b8d321d..0000000000 --- a/awx/ui/client/src/job-detail/job-detail.block.less +++ /dev/null @@ -1,240 +0,0 @@ -/** @define SetupItem */ - -@import '../shared/branding/colors.less'; -@import '../shared/branding/colors.default.less'; -@import '../shared/layouts/one-plus-two.less'; - -@breakpoint-md: 1200px; -@breakpoint-sm: 623px; - -.JobDetail-tasks.section{ - margin-top:40px; -} -.JobDetail-instructions{ - color: @default-interface-txt; - margin: 10px 0 10px 0; - - .badge { - background-color: @default-list-header-bg; - color: @default-interface-txt; - padding: 5px 7px; - } -} -.JobDetail{ - .OnePlusTwo-container(100%, @breakpoint-md); - - &.fullscreen { - .JobDetail-rightSide { - max-width: 100%; - } - } -} - -.JobDetail-leftSide{ - .OnePlusTwo-left--panel(100%, @breakpoint-md); -} - -.JobDetail-rightSide{ - .OnePlusTwo-right--panel(100%, @breakpoint-md); - @media (max-width: @breakpoint-md - 1px) { - padding-right: 15px; - } -} -.JobDetail-panelHeader{ - display: flex; - height: 30px; -} -.JobDetail-expandContainer{ - flex: 1; - margin: 0px; - line-height: 30px; - white-space: nowrap; -} - -.JobDetail-panelHeaderText{ - color: @default-interface-txt; - flex: 1 0 auto; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; -} - -.JobDetail-panelHeaderText:hover{ - color: @default-interface-txt; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; -} - -.JobDetail-expandArrow{ - color: @default-icon-hov; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; - margin-left: 10px; -} - -.JobDetail-resultsDetails{ - display: flex; - flex-wrap: wrap; - flex-direction: row; - padding-top: 25px; - @media screen and(max-width: @breakpoint-sm){ - flex-direction: column; - } -} - -.JobDetail-resultRow{ - width: 100%; - display: flex; - padding-bottom: 10px; - flex-wrap: wrap; -} - -.JobDetail-resultRow--variables { - flex-direction: column; -} - -.JobDetail-resultRowLabel{ - text-transform: uppercase; - color: @default-interface-txt; - font-size: 14px; - font-weight: normal!important; - width: 30%; - margin-right: 20px; - @media screen and(max-width: @breakpoint-md){ - flex: 2.5 0 auto; - } -} - -.JobDetail-resultRowLabel--fullWidth { - width: 100%; - margin-right: 0px; -} - -.JobDetail-resultRowText{ - width: ~"calc(70% - 20px)"; - flex: 1 0 auto; - text-transform: none; - word-wrap: break-word; -} - -.JobDetail-resultRowText--fullWidth { - width: 100%; -} - -.JobDetail-searchHeaderRow{ - display: flex; - flex-wrap: wrap; - flex-direction: row; - height: 50px; - margin-top: 20px; - @media screen and(max-width: @breakpoint-sm){ - height: auto; - } -} - -.JobDetail-searchContainer{ - flex: 2 0 auto; - @media screen and(max-width: @breakpoint-sm){ - margin-bottom: 0px; - } -} - -.JobDetail-tableToggleContainer{ - flex: 1 0 auto; - display: flex; - justify-content: flex-end; -} - -.JobDetail-tableToggle{ - padding-left:10px; - padding-right: 10px; - border: 1px solid @d7grey; -} - -.JobDetail-tableToggle.active{ - background-color: @default-link; - border: 1px solid @default-link; - color: @default-bg; - - &:hover { - background-color: @default-link-hov; - } -} -.JobDetail .nvd3.nv-noData{ - color: @default-interface-txt; - font-size: 12px; - text-transform: uppercase; - font-family: 'Open Sans', sans-serif; -} -.JobDetail .nv-series{ - padding-right: 30px; - display: block; -} -.JobDetail-instructions .badge{ - background-color: @default-list-header-bg; - color: @default-interface-txt; -} -.JobDetail-tableToggle--left{ - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; -} - -.JobDetail-tableToggle--right{ - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; -} - -.JobDetail-searchInput{ - border-radius: 5px !important; -} - -.JobDetail-tableHeader:last-of-type{ - text-align:justify; -} - -.JobDetail-statusIcon{ - padding-right: 10px; - padding-left: 10px; -} - -.JobDetail-tableRow--selected, -.JobDetail-tableRow--selected > :first-child{ - border-left: 5px solid @list-row-select-bord; -} - -.JobDetail-tableRow--selected > :first-child > .JobDetail-statusIcon{ - margin-left: -5px; -} - -.JobDetails-table--noResults { - tr > td { - border-top: none !important; - } -} - -.JobDetail-statusIcon--results{ - padding-left: 0px; - padding-right: 10px; -} - -.JobDetail-graphSection{ - height: 320px; - width:100%; -} - -.JobDetail-stdoutActionButton--active{ - display: none; - visibility: hidden; - flex:none; - width:0px; - padding-right: 0px; -} - -.JobDetail-leftSide.JobDetail-stdoutActionButton--active { - margin-right: 0px; -} diff --git a/awx/ui/client/src/job-detail/job-detail.partial.html b/awx/ui/client/src/job-detail/job-detail.partial.html deleted file mode 100644 index bd0b488b18..0000000000 --- a/awx/ui/client/src/job-detail/job-detail.partial.html +++ /dev/null @@ -1,435 +0,0 @@ -
-
-
- -
- -
-
- -
- - - -
-
- -
-
- -
{{ job_status.status_label }}
-
- -
- -
-
Previous Task Failed - - - - -
-
- -
- -
-
- -
- - -
- -
- -
{{ job_status.started | longDate }}
-
- -
- -
{{ job_type }}
-
- -
- -
{{ job_status.finished | longDate }}
-
- -
- - -
- -
- -
{{ job_status.elapsed }}
-
- -
- - -
- -
- - -
- -
- - -
- -
- -
{{ job.playbook }}
-
- -
- - -
- -
- - -
- -
- - -
- -
- -
{{ job.forks }}
-
- -
- -
{{ job.limit }}
-
- -
- -
{{ verbosity }}
-
- -
- -
{{ job.job_tags }}
-
- -
- -
{{ job.skip_tags }}
-
- -
- - -
- -
-
- - - -
- - -
-
-
1 Please select from a play below to view its associated tasks.
-
-
-
- - - -
-
-
-
- - -
-
-
- - -
- - - - - - - - -
PlaysStartedElapsed
-
-
- - - - - - - - - - - - - - - - - - -
{{ play.name }}{{ play.created | date: 'HH:mm:ss' }}{{ play.elapsed }}
Waiting...
Loading...
No matching plays
-
-
- -
-
- - -
-
2 Please select a task below to view its associated hosts
-
-
-
- - - -
-
-
-
- - -
-
-
- -
- - - - - - - - - -
TasksStartedElapsed
-
-
- - - - - - - - - - - - - - - - - - - -
{{ task.name }}{{ task.created | date: 'HH:mm:ss' }}{{ task.elapsed }}
Waiting...
Loading...
No matching tasks
-
-
-
- - -
-
3 Please select a host below to view associated task details.
-
-
-
- - - -
-
-
-
- - -
-
-
-
- - - - - - - - -
HostsItemMessage
-
- -
- - - - - - - - - - - - - - - - - -
- {{ result.name }}{{ result.name }} - {{ result.item }}{{ result.msg }}
Waiting...
Loading...
No matching host events
-
-
-
- -
-
- - - - - - - -
- - - -
-
-
-
STANDARD OUT
-
- - - - -
-
- -
-
- -
-
- - - - diff --git a/awx/ui/client/src/job-detail/job-detail.route.js b/awx/ui/client/src/job-detail/job-detail.route.js deleted file mode 100644 index 94088c126b..0000000000 --- a/awx/ui/client/src/job-detail/job-detail.route.js +++ /dev/null @@ -1,91 +0,0 @@ -// <<<<<<< 4cf6a946a1aa14b7d64a8e1e8dabecfd3d056f27 -// //<<<<<<< bc59236851902d7c768aa26abdb7dc9c9dc27a5a -// /************************************************* -// * Copyright (c) 2016 Ansible, Inc. -// * -// * All Rights Reserved -// *************************************************/ -// -// // <<<<<<< a3d9eea2c9ddb4e16deec9ec38dea16bf37c559d -// // import { templateUrl } from '../shared/template-url/template-url.factory'; -// // -// // export default { -// // name: 'jobDetail', -// // url: '/jobs/{id: int}', -// // ncyBreadcrumb: { -// // parent: 'jobs', -// // label: "{{ job.id }} - {{ job.name }}" -// // }, -// // data: { -// // socket: { -// // "groups": { -// // "jobs": ["status_changed", "summary"], -// // "job_events": [] -// // } -// // } -// // }, -// // templateUrl: templateUrl('job-detail/job-detail'), -// // controller: 'JobDetailController' -// // }; -// // ======= -// // import {templateUrl} from '../shared/template-url/template-url.factory'; -// // -// // export default { -// // name: 'jobDetail', -// // url: '/jobs/:id', -// // ncyBreadcrumb: { -// // parent: 'jobs', -// // label: "{{ job.id }} - {{ job.name }}" -// // }, -// // socket: { -// // "groups":{ -// // "jobs": ["status_changed", "summary"], -// // "job_events": [] -// // } -// // }, -// // templateUrl: templateUrl('job-detail/job-detail'), -// // controller: 'JobDetailController' -// // }; -// //======= -// ======= -// >>>>>>> Rebase of devel (w/ channels) + socket rework for new job details -// // /************************************************* -// // * Copyright (c) 2016 Ansible, Inc. -// // * -// // * All Rights Reserved -// // *************************************************/ -// // -// // import {templateUrl} from '../shared/template-url/template-url.factory'; -// // -// // export default { -// // name: 'jobDetail', -// // url: '/jobs/:id', -// // ncyBreadcrumb: { -// // parent: 'jobs', -// // label: "{{ job.id }} - {{ job.name }}" -// // }, -// // socket: { -// // "groups":{ -// // "jobs": ["status_changed", "summary"], -// // "job_events": [] -// // } -// // }, -// // resolve: { -// // jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) { -// // if (!$rootScope.event_socket) { -// // $rootScope.event_socket = Socket({ -// // scope: $rootScope, -// // endpoint: "job_events" -// // }); -// // $rootScope.event_socket.init(); -// // // returns should really be providing $rootScope.event_socket -// // // otherwise, we have to inject the entire $rootScope into the controller -// // return true; -// // } else { -// // return true; -// // } -// // }] -// // }, -// // templateUrl: templateUrl('job-detail/job-detail'), -// // controller: 'JobDetailController' -// // }; diff --git a/awx/ui/client/src/job-detail/job-detail.service.js b/awx/ui/client/src/job-detail/job-detail.service.js deleted file mode 100644 index 8e436a3e96..0000000000 --- a/awx/ui/client/src/job-detail/job-detail.service.js +++ /dev/null @@ -1,215 +0,0 @@ -export default - ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){ - return { - stringifyParams: function(params){ - return _.reduce(params, (result, value, key) => { - return result + key + '=' + value + '&'; - }, ''); - }, - - // the the API passes through Ansible's event_data response - // we need to massage away the verbose & redundant stdout/stderr properties - processJson: function(data){ - // configure fields to ignore - var ignored = [ - 'type', - 'event_data', - 'related', - 'summary_fields', - 'url', - 'ansible_facts', - ]; - // remove ignored properties - var result = _.chain(data).cloneDeep().forEach(function(value, key, collection){ - if (ignored.indexOf(key) > -1){ - delete collection[key]; - } - }).value(); - return result; - }, - // Return Ansible's passed-through response msg on a job_event - processEventMsg: function(event){ - return typeof event.event_data.res === 'object' ? event.event_data.res.msg : event.event_data.res; - }, - // Return only Ansible's passed-through response item on a job_event - processEventItem: function(event){ - try{ - var item = event.event_data.res.item; - return typeof item === 'object' ? JSON.stringify(item) : item; - } - catch(err){return;} - }, - processsEventTip: function(event, status){ - try{ - var string = `Event ID: ${ event.id }
Status: ${ _.capitalize(status.status)}. Click for details`; - return typeof item === 'object' ? JSON.stringify(string) : string; - } - catch(err){return;} - }, - // Generate a helper class for job_event statuses - // the stack for which status to display is - // unreachable > failed > changed > ok - // uses the API's runner events and convenience properties .failed .changed to determine status. - // see: job_event_callback.py for more filters to support - processEventStatus: function(event){ - if (event.event === 'runner_on_unreachable'){ - return { - class: 'HostEvents-status--unreachable', - status: 'unreachable' - }; - } - // equiv to 'runner_on_error' && 'runner on failed' - if (event.failed){ - return { - class: 'HostEvents-status--failed', - status: 'failed' - }; - } - // catch the changed case before ok, because both can be true - if (event.changed){ - return { - class: 'HostEvents-status--changed', - status: 'changed' - }; - } - if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok'){ - return { - class: 'HostEvents-status--ok', - status: 'ok' - }; - } - if (event.event === 'runner_on_skipped'){ - return { - class: 'HostEvents-status--skipped', - status: 'skipped' - }; - } - }, - // Consumes a response from this.getRelatedJobEvents(id, params) - // returns an array for view logic to iterate over to build host result rows - processHostEvents: function(data){ - var self = this; - var results = []; - data.forEach(function(event){ - if (event.event !== 'runner_on_no_hosts'){ - var status = self.processEventStatus(event); - var msg = self.processEventMsg(event); - var item = self.processEventItem(event); - var tip = self.processsEventTip(event, status); - results.push({ - id: event.id, - status: status.status, - status_text: _.capitalize(status.status), - host_id: event.host, - task_id: event.parent, - name: event.event_data.host, - created: event.created, - tip: typeof tip === 'undefined' ? undefined : tip, - msg: typeof msg === 'undefined' ? undefined : msg, - item: typeof item === 'undefined' ? undefined : item - }); - } - }); - return results; - }, - // GET events related to a job run - // e.g. - // ?event=playbook_on_stats - // ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter - getRelatedJobEvents: function(id, params){ - var url = GetBasePath('jobs'); - url = url + id + '/job_events/?' + this.stringifyParams(params); - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - getJobEventChildren: function(uuid){ - var url = GetBasePath('job_events'); - url = `${url}?parent__uuid=${uuid}&order_by=host_name`; - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - // GET job host summaries related to a job run - // e.g. ?page_size=200&order=host_name - getJobHostSummaries: function(id, params){ - var url = GetBasePath('jobs'); - url = url + id + '/job_host_summaries/?' + this.stringifyParams(params); - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - // GET job plays related to a job run - // e.g. ?page_size=200 - getJobPlays: function(id, params){ - var url = GetBasePath('jobs'); - url = url + id + '/job_plays/?' + this.stringifyParams(params); - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - getJobTasks: function(id, params){ - var url = GetBasePath('jobs'); - url = url + id + '/job_tasks/?' + this.stringifyParams(params); - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - getJob: function(params){ - var url = GetBasePath('unified_jobs') + '?' + this.stringifyParams(params); - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - // GET next set of paginated results - // expects 'next' param returned by the API e.g. - // "/api/v1/jobs/51/job_plays/?order_by=id&page=2&page_size=1" - getNextPage: function(url){ - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - } - }; - }]; diff --git a/awx/ui/client/src/job-detail/main.js b/awx/ui/client/src/job-detail/main.js deleted file mode 100644 index 628f537e43..0000000000 --- a/awx/ui/client/src/job-detail/main.js +++ /dev/null @@ -1,24 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -// import route from './job-detail.route'; -import controller from './job-detail.controller'; -import service from './job-detail.service'; -import hostEvents from './host-events/main'; -// import hostEvent from './host-event/main'; -import hostSummary from './host-summary/main'; - -export default - angular.module('jobDetail', [ - hostEvents.name, - // hostEvent.name, - hostSummary.name - ]) - .controller('JobDetailController', controller) - .service('JobDetailService', service); - // .run(['$stateExtender', function($stateExtender) { - // $stateExtender.addState(route); - // }]); From 14d9eb92772bdf3b895b76129d2017e1c1e6d313 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 28 Feb 2017 12:25:57 -0800 Subject: [PATCH 17/39] removing some old stale code from host event stdout after decoupling job-results from job-details --- awx/ui/client/src/app.js | 2 -- .../host-event/host-event.controller.js | 26 ++++--------------- .../host-event/host-event.route.js | 3 --- .../src/job-results/job-results.service.js | 13 ---------- 4 files changed, 5 insertions(+), 39 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 7289c1af85..c479867951 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -46,7 +46,6 @@ import inventories from './inventories/main'; import inventoryScripts from './inventory-scripts/main'; import organizations from './organizations/main'; import managementJobs from './management-jobs/main'; -import jobDetail from './job-detail/main'; import workflowResults from './workflow-results/main'; import jobResults from './job-results/main'; import jobSubmission from './job-submission/main'; @@ -119,7 +118,6 @@ var tower = angular.module('Tower', [ login.name, activityStream.name, footer.name, - jobDetail.name, workflowResults.name, jobResults.name, jobSubmission.name, diff --git a/awx/ui/client/src/job-results/host-event/host-event.controller.js b/awx/ui/client/src/job-results/host-event/host-event.controller.js index e6c5fc71c7..b195d69b09 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.controller.js +++ b/awx/ui/client/src/job-results/host-event/host-event.controller.js @@ -6,48 +6,32 @@ export default - ['$stateParams', '$scope', '$state', 'Wait', 'jobResultsService', 'hostEvent', 'hostResults', - function($stateParams, $scope, $state, Wait, jobResultsService, hostEvent, hostResults){ + ['$stateParams', '$scope', '$state', 'Wait', 'jobResultsService', 'hostEvent', + function($stateParams, $scope, $state, Wait, jobResultsService, hostEvent){ $scope.processEventStatus = jobResultsService.processEventStatus; - $scope.hostResults = []; - // Avoid rendering objects in the details fieldset - // ng-if="processResults(value)" via host-event-details.partial.html $scope.processResults = function(value){ if (typeof value === 'object'){return false;} else {return true;} }; - $scope.isStdOut = function(){ - if ($state.current.name === 'jobDetail.host-event.stdout' || $state.current.name === 'jobDetail.host-event.stderr'){ - return 'StandardOut-preContainer StandardOut-preContent'; - } - }; - /*ignore jslint start*/ + var initCodeMirror = function(el, data, mode){ var container = document.getElementById(el); - var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line + var editor = CodeMirror.fromTextArea(container, { lineNumbers: true, mode: mode }); editor.setSize("100%", 200); editor.getDoc().setValue(data); }; - /*ignore jslint end*/ + $scope.isActiveState = function(name){ return $state.current.name === name; }; - $scope.getActiveHostIndex = function(){ - var result = $scope.hostResults.filter(function( obj ) { - return obj.id === $scope.event.id; - }); - return $scope.hostResults.indexOf(result[0]); - }; - var init = function(){ hostEvent.event_name = hostEvent.event; $scope.event = _.cloneDeep(hostEvent); - $scope.hostResults = hostResults; // grab standard out & standard error if present from the host // event's "res" object, for things like Ansible modules diff --git a/awx/ui/client/src/job-results/host-event/host-event.route.js b/awx/ui/client/src/job-results/host-event/host-event.route.js index eb796fa7df..a1fd784434 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.route.js +++ b/awx/ui/client/src/job-results/host-event/host-event.route.js @@ -18,9 +18,6 @@ var hostEventModal = { id: $stateParams.eventId }).then(function(res) { return res.data.results[0]; }); - }], - hostResults: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) { - return jobResultsService.getJobEventChildren($stateParams.taskId).then(res => res.data.results); }] }, onExit: function() { diff --git a/awx/ui/client/src/job-results/job-results.service.js b/awx/ui/client/src/job-results/job-results.service.js index 3817a922b8..5cf6f488e5 100644 --- a/awx/ui/client/src/job-results/job-results.service.js +++ b/awx/ui/client/src/job-results/job-results.service.js @@ -247,19 +247,6 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo msg: 'Call to ' + url + '. GET returned: ' + status }); }); }, - getJobEventChildren: function(id){ - var url = GetBasePath('job_events'); - url = url + id + '/children/?order_by=host_name'; - Rest.setUrl(url); - return Rest.get() - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, stringifyParams: function(params){ return _.reduce(params, (result, value, key) => { return result + key + '=' + value + '&'; From daa3746ef7352fd9f95919796ea700d0f91c9de7 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 28 Feb 2017 12:35:33 -0800 Subject: [PATCH 18/39] removing helpers/JobDetail.js --- awx/ui/client/src/app.js | 1 - awx/ui/client/src/helpers.js | 2 -- .../src/job-results/host-event/host-event.controller.js | 4 ++-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index c479867951..246df0c7bc 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -189,7 +189,6 @@ var tower = angular.module('Tower', [ 'LogViewerStatusDefinition', 'StandardOutHelper', 'LogViewerOptionsDefinition', - 'JobDetailHelper', 'lrInfiniteScroll', 'LoadConfigHelper', 'PortalJobsListDefinition', diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js index b22938443d..765b81be5f 100644 --- a/awx/ui/client/src/helpers.js +++ b/awx/ui/client/src/helpers.js @@ -12,7 +12,6 @@ import Credentials from "./helpers/Credentials"; import Events from "./helpers/Events"; import Groups from "./helpers/Groups"; import Hosts from "./helpers/Hosts"; -import JobDetail from "./helpers/JobDetail"; import JobSubmission from "./helpers/JobSubmission"; import JobTemplates from "./helpers/JobTemplates"; import Jobs from "./helpers/Jobs"; @@ -38,7 +37,6 @@ export Events, Groups, Hosts, - JobDetail, JobSubmission, JobTemplates, Jobs, diff --git a/awx/ui/client/src/job-results/host-event/host-event.controller.js b/awx/ui/client/src/job-results/host-event/host-event.controller.js index b195d69b09..08e228209b 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.controller.js +++ b/awx/ui/client/src/job-results/host-event/host-event.controller.js @@ -17,14 +17,14 @@ var initCodeMirror = function(el, data, mode){ var container = document.getElementById(el); - var editor = CodeMirror.fromTextArea(container, { + var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line lineNumbers: true, mode: mode }); editor.setSize("100%", 200); editor.getDoc().setValue(data); }; - + /*ignore jslint end*/ $scope.isActiveState = function(name){ return $state.current.name === name; }; From dc5716f0ea51d23e632e3bebea3e593a9fba1648 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 28 Feb 2017 13:44:40 -0800 Subject: [PATCH 19/39] changing jobDetail to jobResults throughout the app --- awx/ui/client/src/app.js | 22 +++++++++---------- .../host-event/host-event-modal.partial.html | 16 +++++++------- .../host-event/host-event.controller.js | 6 ++--- .../host-event/host-event.route.js | 8 +++---- .../src/job-results/job-results.partial.html | 2 +- .../src/job-results/job-results.route.js | 2 +- .../src/job-results/job-results.service.js | 10 ++++----- .../src/job-results/parse-stdout.service.js | 2 +- .../launchjob.factory.js | 14 ++++++------ .../job-submission.controller.js | 18 +++++++-------- .../client/src/jobs/jobs-list.controller.js | 16 +++++++------- awx/ui/client/src/lists/AllJobs.js | 8 +++---- awx/ui/client/src/lists/CompletedJobs.js | 6 ++--- awx/ui/client/src/lists/Jobs.js | 6 ++--- .../organizations-projects.controller.js | 8 +++---- .../projects/list/projects-list.controller.js | 8 +++---- .../adhoc/standard-out-adhoc.partial.html | 6 ++--- .../standard-out-inventory-sync.partial.html | 6 ++--- .../log/standard-out-log.controller.js | 2 +- .../standard-out-management-jobs.partial.html | 4 ++-- .../standard-out-scm-update.partial.html | 6 ++--- .../standard-out/standard-out.controller.js | 6 ++--- .../workflow-chart.directive.js | 2 +- 23 files changed, 92 insertions(+), 92 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 246df0c7bc..f17df84b89 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -347,19 +347,19 @@ var tower = angular.module('Tower', [ $rootScope.$on("$stateChangeStart", function (event, next) { // Remove any lingering intervals - // except on jobDetails.* states - var jobDetailStates = [ - 'jobDetail', - 'jobDetail.host-summary', - 'jobDetail.host-event.details', - 'jobDetail.host-event.json', - 'jobDetail.host-events', - 'jobDetail.host-event.stdout' + // except on jobResults.* states + var jobResultStates = [ + 'jobResult', + 'jobResult.host-summary', + 'jobResult.host-event.details', + 'jobResult.host-event.json', + 'jobResult.host-events', + 'jobResult.host-event.stdout' ]; - if ($rootScope.jobDetailInterval && !_.includes(jobDetailStates, next.name) ) { - window.clearInterval($rootScope.jobDetailInterval); + if ($rootScope.jobResultInterval && !_.includes(jobResultStates, next.name) ) { + window.clearInterval($rootScope.jobResultInterval); } - if ($rootScope.jobStdOutInterval && !_.includes(jobDetailStates, next.name) ) { + if ($rootScope.jobStdOutInterval && !_.includes(jobResultStates, next.name) ) { window.clearInterval($rootScope.jobStdOutInterval); } diff --git a/awx/ui/client/src/job-results/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-results/host-event/host-event-modal.partial.html index cf9dc67ac4..f8d488b4b3 100644 --- a/awx/ui/client/src/job-results/host-event/host-event-modal.partial.html +++ b/awx/ui/client/src/job-results/host-event/host-event-modal.partial.html @@ -9,7 +9,7 @@ {{event.host_name}} - @@ -40,19 +40,19 @@
- - - @@ -64,7 +64,7 @@
- +
diff --git a/awx/ui/client/src/job-results/host-event/host-event.controller.js b/awx/ui/client/src/job-results/host-event/host-event.controller.js index 08e228209b..781f971793 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.controller.js +++ b/awx/ui/client/src/job-results/host-event/host-event.controller.js @@ -56,7 +56,7 @@ } // instantiate Codemirror // try/catch pattern prevents the abstract-state controller from complaining about element being null - if ($state.current.name === 'jobDetail.host-event.json'){ + if ($state.current.name === 'jobResult.host-event.json'){ try{ initCodeMirror('HostEvent-codemirror', JSON.stringify($scope.json, null, 4), {name: "javascript", json: true}); } @@ -64,7 +64,7 @@ // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController } } - else if ($state.current.name === 'jobDetail.host-event.stdout'){ + else if ($state.current.name === 'jobResult.host-event.stdout'){ try{ initCodeMirror('HostEvent-codemirror', $scope.stdout, 'shell'); } @@ -72,7 +72,7 @@ // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController } } - else if ($state.current.name === 'jobDetail.host-event.stderr'){ + else if ($state.current.name === 'jobResult.host-event.stderr'){ try{ initCodeMirror('HostEvent-codemirror', $scope.stderr, 'shell'); } diff --git a/awx/ui/client/src/job-results/host-event/host-event.route.js b/awx/ui/client/src/job-results/host-event/host-event.route.js index a1fd784434..650a82bb25 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.route.js +++ b/awx/ui/client/src/job-results/host-event/host-event.route.js @@ -7,7 +7,7 @@ import { templateUrl } from '../../shared/template-url/template-url.factory'; var hostEventModal = { - name: 'jobDetail.host-event', + name: 'jobResult.host-event', url: '/host-event/:eventId', controller: 'HostEventController', templateUrl: templateUrl('job-results/host-event/host-event-modal'), @@ -31,21 +31,21 @@ var hostEventModal = { }; var hostEventJson = { - name: 'jobDetail.host-event.json', + name: 'jobResult.host-event.json', url: '/json', controller: 'HostEventController', templateUrl: templateUrl('job-results/host-event/host-event-codemirror') }; var hostEventStdout = { - name: 'jobDetail.host-event.stdout', + name: 'jobResult.host-event.stdout', url: '/stdout', controller: 'HostEventController', templateUrl: templateUrl('job-results/host-event/host-event-stdout') }; var hostEventStderr = { - name: 'jobDetail.host-event.stderr', + name: 'jobResult.host-event.stderr', url: '/stderr', controller: 'HostEventController', templateUrl: templateUrl('job-results/host-event/host-event-stderr') diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html index e48174311d..c2c56384de 100644 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ b/awx/ui/client/src/job-results/job-results.partial.html @@ -108,7 +108,7 @@ ng-show="!previousTaskFailed"> {{job.job_explanation}} -
Previous Task Failed
- - - + + +
diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html index 451f10c5e8..5df1bd84c7 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html @@ -8,9 +8,9 @@ RESULTS
- - - + + +
diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 45564fffc6..bf8a8f89ef 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -20,7 +20,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce // Open up a socket for events depending on the type of job function openSockets() { - if ($state.current.name === 'jobDetail') { + if ($state.current.name === 'jobResult') { $log.debug("socket watching on job_events-" + job_id); $scope.$on(`ws-job_events-${job_id}`, function() { $log.debug("socket fired on job_events-" + job_id); diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html index bb7a6c1e64..8c9740a5eb 100644 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html +++ b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html @@ -8,8 +8,8 @@ RESULTS
- - + +
diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html index 458b2c6dd2..249cb0bf72 100644 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html +++ b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html @@ -8,9 +8,9 @@ RESULTS
- - - + + +
diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index 64f34cb68f..0cabd74389 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -33,14 +33,14 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { // Go out and refresh the job details - getJobDetails(); + getjobResults(); } }); // Set the parse type so that CodeMirror knows how to display extra params YAML/JSON $scope.parseType = 'yaml'; - function getJobDetails() { + function getjobResults() { // Go out and get the job details based on the job type. jobType gets defined // in the data block of the route declaration for each of the different types @@ -260,7 +260,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name }); }; - getJobDetails(); + getjobResults(); } diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index e7379fe19c..08ea0b4142 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -830,7 +830,7 @@ export default [ '$state','moment', '$timeout', '$window', this.on("click", function(d) { if(d.job.id && d.unifiedJobTemplate) { if(d.unifiedJobTemplate.unified_job_type === 'job') { - $state.go('jobDetail', {id: d.job.id}); + $state.go('jobResult', {id: d.job.id}); } else if(d.unifiedJobTemplate.unified_job_type === 'inventory_update') { $state.go('inventorySyncStdout', {id: d.job.id}); From 66352ce5c9750678572ebce691f1eb8793912adc Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 28 Feb 2017 13:49:06 -0800 Subject: [PATCH 20/39] removing last reference to job detail of any kind --- awx/ui/client/legacy-styles/ansible-ui.less | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 4cc306cb36..763abab7f2 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -1671,7 +1671,7 @@ tr td button i { .modal-body { min-height: 120px; padding: 20px 0; - + .alert { padding: 10px; margin: 0; @@ -1984,10 +1984,6 @@ tr td button i { width: 73px; } -.JobDetails-status { - margin-bottom: 12px; -} - .red-text { color: @red; } From 7f16036171065827a22ffbb4fff57e5ad49fe430 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Wed, 1 Mar 2017 12:56:30 -0800 Subject: [PATCH 21/39] fixing merge conflicts --- awx/ui/client/src/helpers/JobDetail.js | 1198 ----------------- .../host-events/host-events.block.less | 93 -- .../host-events/host-events.controller.js | 48 - .../host-events/host-events.partial.html | 52 - .../host-summary/host-summary.controller.js | 131 -- .../src/job-detail/job-detail.controller.js | 1027 -------------- 6 files changed, 2549 deletions(-) delete mode 100644 awx/ui/client/src/helpers/JobDetail.js delete mode 100644 awx/ui/client/src/job-detail/host-events/host-events.block.less delete mode 100644 awx/ui/client/src/job-detail/host-events/host-events.controller.js delete mode 100644 awx/ui/client/src/job-detail/host-events/host-events.partial.html delete mode 100644 awx/ui/client/src/job-detail/host-summary/host-summary.controller.js delete mode 100644 awx/ui/client/src/job-detail/job-detail.controller.js diff --git a/awx/ui/client/src/helpers/JobDetail.js b/awx/ui/client/src/helpers/JobDetail.js deleted file mode 100644 index ad049ed481..0000000000 --- a/awx/ui/client/src/helpers/JobDetail.js +++ /dev/null @@ -1,1198 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:JobDetail - * @description helper moduler for jobdetails controller - # Playbook events will be structured to form the following hierarchy: - # - playbook_on_start (once for each playbook file) - # - playbook_on_vars_prompt (for each play, but before play starts, we - # currently don't handle responding to these prompts) - # - playbook_on_play_start (once for each play) - # - playbook_on_import_for_host - # - playbook_on_not_import_for_host - # - playbook_on_no_hosts_matched - # - playbook_on_no_hosts_remaining - # - playbook_on_setup - # - runner_on* - # - playbook_on_task_start (once for each task within a play) - # - runner_on_failed - # - runner_on_ok - # - runner_on_error - # - runner_on_skipped - # - runner_on_unreachable - # - runner_on_no_hosts - # - runner_on_async_poll - # - runner_on_async_ok - # - runner_on_async_failed - # - runner_on_file_diff - # - playbook_on_notify (once for each notification from the play) - # - playbook_on_stats - -*/ - - -export default - angular.module('JobDetailHelper', ['Utilities', 'RestServices', 'ModalDialog']) - - .factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', - 'GetElapsed', 'UpdateTaskStatus', 'JobIsFinished', 'AddNewTask', 'AddNewPlay', - function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, GetElapsed, - UpdateTaskStatus, JobIsFinished, AddNewTask, AddNewPlay) { - return function(params) { - - var scope = params.scope, - event = params.event, - msg; - - $log.debug('processing event: ' + event.id); - $log.debug(event); - - function getMsg(event) { - var msg = ''; - if (event.event_data && event.event_data.res) { - if (typeof event.event_data.res === 'object') { - msg = event.event_data.res.msg; - } else { - msg = event.event_data.res; - } - } - return msg; - } - - switch (event.event) { - case 'playbook_on_start': - if (!JobIsFinished(scope)) { - scope.job_status.started = event.created; - scope.job_status.status = 'running'; - } - break; - - case 'playbook_on_play_start': - AddNewPlay({ scope: scope, event: event }); - break; - - case 'playbook_on_setup': - AddNewTask({ scope: scope, event: event }); - break; - - case 'playbook_on_task_start': - AddNewTask({ scope: scope, event: event }); - break; - - case 'runner_on_ok': - case 'runner_on_async_ok': - msg = getMsg(event); - UpdateHostStatus({ - scope: scope, - name: event.host_name, - host_id: event.host, - task_id: event.parent, - status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), - id: event.id, - created: event.created, - modified: event.modified, - message: msg, - counter: event.counter, - item: (event.event_data && event.event_data.res) ? event.event_data.res.item : '' - }); - break; - - case 'playbook_on_no_hosts_matched': - UpdatePlayStatus({ - scope: scope, - play_id: event.parent, - failed: false, - changed: false, - modified: event.modified, - no_hosts: true - }); - break; - - case 'runner_on_unreachable': - msg = getMsg(event); - UpdateHostStatus({ - scope: scope, - name: event.host_name, - host_id: event.host, - task_id: event.parent, - status: 'unreachable', - id: event.id, - created: event.created, - modified: event.modified, - message: msg, - counter: event.counter, - item: (event.event_data && event.event_data.res) ? event.event_data.res.item : '' - }); - break; - - case 'runner_on_error': - case 'runner_on_async_failed': - msg = getMsg(event); - UpdateHostStatus({ - scope: scope, - name: event.host_name, - host_id: event.host, - task_id: event.parent, - status: 'failed', - id: event.id, - created: event.created, - modified: event.modified, - message: msg, - counter: event.counter, - item: (event.event_data && event.event_data.res) ? event.event_data.res.item : '' - }); - break; - - case 'runner_on_no_hosts': - UpdateTaskStatus({ - scope: scope, - failed: event.failed, - changed: event.changed, - task_id: event.parent, - modified: event.modified, - no_hosts: true - }); - break; - - case 'runner_on_skipped': - msg = getMsg(event); - UpdateHostStatus({ - scope: scope, - name: event.host_name, - host_id: event.host, - task_id: event.parent, - status: 'skipped', - id: event.id, - created: event.created, - modified: event.modified, - message: msg, - counter: event.counter, - item: (event.event_data && event.event_data.res) ? event.event_data.res.item : '' - }); - } - }; - }]) - - .factory('JobIsFinished', [ function() { - return function(scope) { - return (scope.job_status.status === 'failed' || scope.job_status.status === 'canceled' || - scope.job_status.status === 'error' || scope.job_status.status === 'successful'); - }; - }]) - - .factory('GetElapsed', [function() { - return function(params) { - var start = params.start, - end = params.end, - dt1, dt2, sec, hours, min; - dt1 = new Date(start); - dt2 = new Date(end); - if ( dt2.getTime() !== dt1.getTime() ) { - sec = Math.floor( (dt2.getTime() - dt1.getTime()) / 1000 ); - hours = Math.floor(sec / 3600); - sec = sec - (hours * 3600); - if (('' + hours).length < 2) { - hours = ('00' + hours).substr(-2, 2); - } - min = Math.floor(sec / 60); - sec = sec - (min * 60); - min = ('00' + min).substr(-2,2); - sec = ('00' + sec).substr(-2,2); - return hours + ':' + min + ':' + sec; - } - else { - return '00:00:00'; - } - }; - }]) - - .factory('SetActivePlay', [ function() { - return function(params) { - //find the most recent task in the list of 'active' tasks - - var scope = params.scope, - activeList = [], - newActivePlay, - key; - - for (key in scope.jobData.plays) { - if (scope.jobData.plays[key].taskCount > 0) { - activeList.push(key); - } - } - - if (activeList.length > 0) { - newActivePlay = scope.jobData.plays[activeList[activeList.length - 1]].id; - if (newActivePlay && scope.activePlay && newActivePlay !== scope.activePlay) { - scope.jobData.plays[scope.activePlay].tasks = {}; - scope.jobData.plays[scope.activePlay].playActiveClass = ''; - scope.activeTask = null; - } - if (newActivePlay) { - scope.activePlay = newActivePlay; - scope.jobData.plays[scope.activePlay].playActiveClass = 'JobDetail-tableRow--selected'; - } - } - }; - }]) - - .factory('SetActiveTask', [ function() { - return function(params) { - //find the most recent task in the list of 'active' tasks - var scope = params.scope, - key, - newActiveTask, - activeList = []; - - for (key in scope.jobData.plays[scope.activePlay].tasks) { - if (scope.jobData.plays[scope.activePlay].tasks[key].reportedHosts > 0 || scope.jobData.plays[scope.activePlay].tasks[key].status === 'no-matching-hosts') { - activeList.push(key); - } - } - - if (activeList.length > 0) { - newActiveTask = scope.jobData.plays[scope.activePlay].tasks[activeList[activeList.length - 1]].id; - if (newActiveTask && scope.activeTask && newActiveTask !== scope.activeTask) { - if (scope.activeTask && scope.jobData.plays[scope.activePlay].tasks[scope.activeTask] !== undefined) { - scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = ''; - scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults = {}; - } - } - if (newActiveTask) { - scope.activeTask = newActiveTask; - scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = 'JobDetail-tableRow--selected'; - } - } - }; - }]) - - .factory('AddNewPlay', ['SetActivePlay', function(SetActivePlay) { - return function(params) { - var scope = params.scope, - event = params.event, - status, status_text; - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - - scope.jobData.plays[event.id] = { - id: event.id, - name: event.play, - created: event.created, - status: status, - status_text: status_text, - elapsed: '00:00:00', - hostCount: 0, - taskCount: 0, - fistTask: null, - unreachableCount: 0, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - tasks: {} - }; - - SetActivePlay({ scope: scope }); - }; - }]) - - .factory('AddNewTask', ['UpdatePlayStatus', 'SetActivePlay', 'SetActiveTask', function(UpdatePlayStatus, SetActivePlay, SetActiveTask) { - return function(params) { - var scope = params.scope, - event = params.event, - status, status_text; - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - - scope.jobData.plays[event.parent].tasks[event.id] = { - id: event.id, - play_id: event.parent, - name: (event.task) ? event.task : event.event_display, - status: status, - status_text: status_text, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - created: event.created, - modified: event.modified, - hostCount: (scope.jobData.plays[event.parent]) ? scope.jobData.plays[event.parent].hostCount : 0, - reportedHosts: 0, - successfulCount: 0, - failedCount: 0, - changedCount: 0, - skippedCount: 0, - unreachableCount: 0, - successfulStyle: { display: 'none'}, - failedStyle: { display: 'none' }, - changedStyle: { display: 'none' }, - skippedStyle: { display: 'none' }, - unreachableStyle: { display: 'none' }, - hostResults: {} - }; - - if (scope.jobData.plays[event.parent].firstTask === undefined || scope.jobData.plays[event.parent].firstTask === null) { - scope.jobData.plays[event.parent].firstTask = event.id; - } - scope.jobData.plays[event.parent].taskCount++; - - SetActivePlay({ scope: scope }); - - SetActiveTask({ scope: scope }); - - UpdatePlayStatus({ - scope: scope, - play_id: event.parent, - failed: event.failed, - changed: event.changed, - modified: event.modified - }); - }; - }]) - - .factory('UpdateJobStatus', ['GetElapsed', 'Empty', 'JobIsFinished', 'longDateFilter', function(GetElapsed, Empty, JobIsFinished, longDateFilter) { - return function(params) { - var scope = params.scope, - failed = params.failed, - modified = params.modified, - started = params.started, - finished = params.finished; - - if (failed && scope.job_status.status !== 'failed' && scope.job_status.status !== 'error' && - scope.job_status.status !== 'canceled') { - scope.job_status.status = 'failed'; - } - if (JobIsFinished(scope) && !Empty(modified)) { - scope.job_status.finished = longDateFilter(modified); - } - if (!Empty(started) && Empty(scope.job_status.started)) { - scope.job_status.started = longDateFilter(modified); - } - if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) { - scope.job_status.elapsed = GetElapsed({ - start: started, - end: finished - }); - } - }; - }]) - - // Update the status of a play - .factory('UpdatePlayStatus', ['GetElapsed', 'UpdateJobStatus', function(GetElapsed, UpdateJobStatus) { - return function(params) { - var scope = params.scope, - failed = params.failed, - changed = params.changed, - id = params.play_id, - modified = params.modified, - no_hosts = params.no_hosts, - play; - - if (scope.jobData.plays[id] !== undefined) { - play = scope.jobData.plays[id]; - if (failed) { - play.status = 'failed'; - play.status_text = 'Failed'; - } - else if (play.status !== 'changed' && play.status !== 'failed') { - // once the status becomes 'changed' or 'failed' don't modify it - if (no_hosts) { - play.status = 'no-matching-hosts'; - play.status_text = 'No matching hosts'; - } else { - play.status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful'; - play.status_text = (changed) ? 'Changed' : (failed) ? 'Failed' : 'OK'; - } - } - play.taskCount = (play.taskCount > 0) ? play.taskCount : 1; // set to a minimum of 1 to force drawing - play.status_tip = "Event ID: " + play.id + "
Status: " + play.status_text; - play.finished = modified; - play.elapsed = GetElapsed({ - start: play.created, - end: modified - }); - //play.status_text = (status_text) ? status_text : play.status; - } - - UpdateJobStatus({ - scope: scope, - failed: null, - modified: modified - }); - }; - }]) - - .factory('UpdateTaskStatus', ['UpdatePlayStatus', 'GetElapsed', function(UpdatePlayStatus, GetElapsed) { - return function(params) { - var scope = params.scope, - failed = params.failed, - changed = params.changed, - id = params.task_id, - modified = params.modified, - no_hosts = params.no_hosts, - play, task; - - // find the task in our hierarchy - for (play in scope.jobData.plays) { - if (scope.jobData.plays[play].tasks[id]) { - task = scope.jobData.plays[play].tasks[id]; - } - } - - if (task) { - if (no_hosts){ - task.status = 'no-matching-hosts'; - task.status_text = 'No matching hosts'; - } - else if (failed) { - task.status = 'failed'; - task.status_text = 'Failed'; - } - else if (task.status !== 'changed' && task.status !== 'failed') { - // once the status becomes 'changed' or 'failed' don't modify it - task.status = (failed) ? 'failed' : (changed) ? 'changed' : 'successful'; - task.status_text = (failed) ? 'Failed' : (changed) ? 'Changed' : 'OK'; - } - task.status_tip = "Event ID: " + task.id + "
Status: " + task.status_text; - task.finished = params.modified; - task.elapsed = GetElapsed({ - start: task.created, - end: modified - }); - - UpdatePlayStatus({ - scope: scope, - failed: failed, - changed: changed, - play_id: task.play_id, - modified: modified, - no_hosts: no_hosts - }); - } - }; - }]) - - // Each time a runner event is received update host summary totals and the parent task - .factory('UpdateHostStatus', ['UpdateTaskStatus', 'AddHostResult', function(UpdateTaskStatus, AddHostResult) { - return function(params) { - var scope = params.scope, - status = params.status, // successful, changed, unreachable, failed, skipped - name = params.name, - event_id = params.id, - host_id = params.host_id, - task_id = params.task_id, - modified = params.modified, - created = params.created, - msg = params.message, - item = params.item, - counter = params.counter; - - if (scope.jobData.hostSummaries[host_id] !== undefined) { - scope.jobData.hostSummaries[host_id].ok += (status === 'successful') ? 1 : 0; - scope.jobData.hostSummaries[host_id].changed += (status === 'changed') ? 1 : 0; - scope.jobData.hostSummaries[host_id].unreachable += (status === 'unreachable') ? 1 : 0; - scope.jobData.hostSummaries[host_id].failed += (status === 'failed') ? 1 : 0; - if (status === 'failed' || status === 'unreachable') { - scope.jobData.hostSummaries[host_id].status = 'failed'; - } - } - else { - scope.jobData.hostSummaries[host_id] = { - id: host_id, - name: name, - ok: (status === 'successful') ? 1 : 0, - changed: (status === 'changed') ? 1 : 0, - unreachable: (status === 'unreachable') ? 1 : 0, - failed: (status === 'failed') ? 1 : 0, - status: (status === 'failed' || status === 'unreachable') ? 'failed' : 'successful' - }; - } - UpdateTaskStatus({ - scope: scope, - task_id: task_id, - failed: ((status === 'failed' || status === 'unreachable') ? true :false), - changed: ((status === 'changed') ? true : false), - modified: modified - }); - - AddHostResult({ - scope: scope, - task_id: task_id, - host_id: host_id, - event_id: event_id, - status: status, - name: name, - created: created, - counter: counter, - message: msg, - item: item - }); - }; - }]) - - // Add a new host result - .factory('AddHostResult', ['SetTaskStyles', 'SetActivePlay', 'SetActiveTask', function(SetTaskStyles, SetActivePlay, SetActiveTask) { - return function(params) { - var scope = params.scope, - task_id = params.task_id, - host_id = params.host_id, - event_id = params.event_id, - status = params.status, - created = params.created, - counter = params.counter, - name = params.name, - msg = params.message, - item = params.item, - status_text = '', - task, play, play_id; - - switch(status) { - case "successful": - status_text = 'OK'; - break; - case "changed": - status_text = "Changed"; - break; - case "failed": - status_text = "Failed"; - break; - case "unreachable": - status_text = "Unreachable"; - break; - case "skipped": - status_text = "Skipped"; - } - - if (typeof item === "object") { - item = JSON.stringify(item); - } - - for (play in scope.jobData.plays) { - for (task in scope.jobData.plays[play].tasks) { - if (parseInt(task,10) === parseInt(task_id,10)) { - play_id = parseInt(play,10); - } - } - } - - if (play_id) { - scope.jobData.plays[play_id].tasks[task_id].hostResults[event_id] = { - id: event_id, - status: status, - status_text: status_text, - host_id: host_id, - task_id: task_id, - name: name, - created: created, - counter: counter, - msg: msg, - item: item - }; - - // increment the unreachable count on the play - if (status === 'unreachable') { - scope.jobData.plays[play_id].unreachableCount++; - } - - // update the task status bar - task = scope.jobData.plays[play_id].tasks[task_id]; - - if (task_id === scope.jobData.plays[play_id].firstTask) { - scope.jobData.plays[play_id].hostCount++; - task.hostCount++; - } - - task.reportedHosts += 1; - task.failedCount += (status === 'failed') ? 1 : 0; - task.changedCount += (status === 'changed') ? 1 : 0; - task.successfulCount += (status === 'successful') ? 1 : 0; - task.skippedCount += (status === 'skipped') ? 1 : 0; - task.unreachableCount += (status === 'unreachable') ? 1 : 0; - - SetTaskStyles({ - task: task - }); - - SetActivePlay({ scope: scope }); - - SetActiveTask({ scope: scope }); - } - }; - }]) - - .factory('SetTaskStyles', [ function() { - return function(params) { - var task = params.task, - diff; - - task.missingCount = task.hostCount - (task.failedCount + task.changedCount + task.skippedCount + task.successfulCount + - task.unreachableCount); - if(task.missingCount<0){ - task.hostCount = (task.failedCount + task.changedCount + task.skippedCount + task.successfulCount + - task.unreachableCount); - } - task.missingPct = (task.hostCount > 0) ? Math.ceil((100 * (task.missingCount / task.hostCount))) : 0; - task.failedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.failedCount / task.hostCount))) : 0; - task.changedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.changedCount / task.hostCount))) : 0; - task.skippedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.skippedCount / task.hostCount))) : 0; - task.successfulPct = (task.hostCount > 0) ? Math.ceil((100 * (task.successfulCount / task.hostCount))) : 0; - task.unreachablePct = (task.hostCount > 0) ? Math.ceil((100 * (task.unreachableCount / task.hostCount))) : 0; - - // cap % at 100 - task.missingPct = (task.missingPct > 100) ? 100 : task.missingPct; - task.failedPct = (task.failedPct > 100) ? 100 : task.failedPct; - task.changedPct = (task.changedPct > 100) ? 100 : task.changedPct; - task.skippedPct = (task.skippedPct > 100) ? 100 : task.skippedPct; - task.successfulPct = ( task.successfulPct > 100) ? 100 : task.successfulPct; - task.unreachablePct = (task.unreachablePct > 100) ? 100 : task.unreachablePct; - - diff = (task.failedPct + task.changedPct + task.skippedPct + task.successfulPct + task.unreachablePct + task.missingPct) - 100; - if (diff > 0) { - if (task.failedPct > diff) { - task.failedPct = task.failedPct - diff; - } - else if (task.changedPct > diff) { - task.changedPct = task.changedPct - diff; - } - else if (task.skippedPct > diff) { - task.skippedPct = task.skippedPct - diff; - } - else if (task.successfulPct > diff) { - task.successfulPct = task.successfulPct - diff; - } - else if (task.unreachablePct > diff) { - task.unreachablePct = task.unreachablePct - diff; - } - else if (task.missingPct > diff) { - task.missingPct = task.missingPct - diff; - } - } - task.successfulStyle = (task.successfulPct > 0) ? { 'display': 'inline-block' }: { 'display': 'none' }; - task.changedStyle = (task.changedPct > 0) ? { 'display': 'inline-block'} : { 'display': 'none' }; - task.skippedStyle = (task.skippedPct > 0) ? { 'display': 'inline-block' } : { 'display': 'none' }; - task.failedStyle = (task.failedPct > 0) ? { 'display': 'inline-block' } : { 'display': 'none' }; - task.unreachableStyle = (task.unreachablePct > 0) ? { 'display': 'inline-block' } : { 'display': 'none' }; - task.missingStyle = (task.missingPct > 0) ? { 'display': 'inline-block' } : { 'display': 'none' }; - }; - }]) - - .factory('LoadPlays', ['Rest', 'ProcessErrors', 'GetElapsed', 'SelectPlay', 'JobIsFinished', - function(Rest, ProcessErrors, GetElapsed, SelectPlay, JobIsFinished) { - return function(params) { - var scope = params.scope, - callback = params.callback, - url; - - scope.plays = []; - - scope.playsLoading = true; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - scope.next_plays = data.next; - scope.plays = []; - data.results.forEach(function(event, idx) { - var status, status_text, start, end, elapsed; - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - start = event.started; - - if (idx < data.results.length - 1) { - // end date = starting date of the next event - end = data.results[idx + 1].started; - } - else if (JobIsFinished(scope)) { - // this is the last play and the job already finished - end = scope.job_status.finished; - } - if (end) { - elapsed = GetElapsed({ - start: start, - end: end - }); - } - else { - elapsed = '00:00:00'; - } - - scope.plays.push({ - id: event.id, - name: event.play, - created: start, - finished: end, - status: status, - status_text: status_text, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - elapsed: elapsed, - hostCount: 0, - fistTask: null, - playActiveClass: '', - unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0, - }); - }); - - // set the active task - SelectPlay({ - scope: scope, - id: (scope.plays.length > 0) ? scope.plays[0].id : null, - callback: callback - }); - scope.playsLoading = false; - }) - .error(function(data) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }; - }]) - - // Call when the selected Play needs to change - .factory('SelectPlay', ['LoadTasks', function(LoadTasks) { - return function(params) { - var scope = params.scope, - id = params.id, - callback = params.callback; - - scope.selectedPlay = id; - scope.plays.forEach(function(play, idx) { - if (play.id === scope.selectedPlay) { - scope.plays[idx].playActiveClass = 'JobDetail-tableRow--selected'; - } - else { - scope.plays[idx].playActiveClass = ''; - } - }); - - LoadTasks({ - scope: scope, - callback: callback, - clear: true - }); - - }; - }]) - - .factory('LoadTasks', ['Rest', 'ProcessErrors', 'GetElapsed', 'SelectTask', 'SetTaskStyles', function(Rest, ProcessErrors, GetElapsed, SelectTask, SetTaskStyles) { - return function(params) { - var scope = params.scope, - callback = params.callback, - url, play; - - scope.tasks = []; - if (scope.selectedPlay) { - url = scope.job.url + 'job_tasks/?event_id=' + scope.selectedPlay; - - scope.plays.every(function(p, idx) { - if (p.id === scope.selectedPlay) { - play = scope.plays[idx]; - return false; - } - return true; - }); - - scope.tasksLoading = true; - - Rest.setUrl(url); - Rest.get() - .success(function(data) { - scope.next_tasks = data.next; - scope.tasks = []; - data.results.forEach(function(event, idx) { - var end, elapsed, status, status_text; - - if (play.firstTask === undefined || play.firstTask === null) { - play.firstTask = event.id; - play.hostCount = (event.host_count) ? event.host_count : 0; - } - - if (idx < data.results.length - 1) { - // end date = starting date of the next event - end = data.results[idx + 1].created; - } - else { - // no next event (task), get the end time of the play - scope.plays.every(function(play) { - if (play.id === scope.selectedPlay) { - end = play.finished; - return false; - } - return true; - }); - } - - if (end) { - elapsed = GetElapsed({ - start: event.created, - end: end - }); - } - else { - elapsed = '00:00:00'; - } - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - - scope.tasks.push({ - id: event.id, - play_id: scope.selectedPlay, - name: event.name, - status: status, - status_text: status_text, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - created: event.created, - modified: event.modified, - finished: end, - elapsed: elapsed, - hostCount: (event.host_count) ? event.host_count : 0, - reportedHosts: (event.reported_hosts) ? event.reported_hosts : 0, - successfulCount: (event.successful_count) ? event.successful_count : 0, - failedCount: (event.failed_count) ? event.failed_count : 0, - changedCount: (event.changed_count) ? event.changed_count : 0, - skippedCount: (event.skipped_count) ? event.skipped_count : 0, - unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0, - taskActiveClass: '' - }); - - if (play.firstTask !== event.id) { - // this is not the first task - scope.tasks[scope.tasks.length - 1].hostCount = play.hostCount; - } - - SetTaskStyles({ - task: scope.tasks[scope.tasks.length - 1] - }); - }); - - // set the active task - SelectTask({ - scope: scope, - id: (scope.tasks.length > 0) ? scope.tasks[0].id : null, - callback: callback - }); - - scope.tasksLoading = false; - - }) - .error(function(data) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - } - else { - SelectTask({ - scope: scope, - id: null, - callback: callback - }); - } - }; - }]) - - // Call when the selected task needs to change - .factory('SelectTask', ['JobDetailService', function(JobDetailService) { - return function(params) { - var scope = params.scope, - id = params.id; - - scope.selectedTask = id; - scope.tasks.forEach(function(task, idx) { - if (task.id === scope.selectedTask) { - scope.tasks[idx].taskActiveClass = 'JobDetail-tableRow--selected'; - } - else { - scope.tasks[idx].taskActiveClass = ''; - } - }); - if (scope.selectedTask !== null){ - params = { - parent: scope.selectedTask, - event__startswith: 'runner', - page_size: scope.hostResultsMaxRows, - order: 'host_name,counter', - }; - - JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){ - scope.hostResults = JobDetailService.processHostEvents(res.results); - scope.hostResultsLoading = false; - }); - } - else{ - scope.hostResults = []; - scope.hostResultsLoading = false; - } - }; - }]) - - .factory('DrawGraph', ['DonutChart', function(DonutChart) { - return function(params) { - var count = params.count, - graph_data = []; - // Ready the data - if (count.ok.length > 0) { - graph_data.push({ - label: 'OK', - value: count.ok.length, - color: '#5CB85C' - }); - } - if (count.changed.length > 0) { - graph_data.push({ - label: 'CHANGED', - value: count.changed.length, - color: '#FF9900' - }); - } - if (count.unreachable.length > 0) { - graph_data.push({ - label: 'UNREACHABLE', - value: count.unreachable.length, - color: '#FF0000' - }); - } - if (count.failures.length > 0) { - graph_data.push({ - label: 'FAILED', - value: count.failures.length, - color: '#D9534F' - }); - } - DonutChart({ - data: graph_data - }); - }; - }]) - - .factory('DonutChart', [function() { - return function(params) { - var dataset = params.data, - element = $("#graph-section"), - colors, total,job_detail_chart; - - colors = _.map(dataset, function(d){ - return d.color; - }); - total = d3.sum(dataset.map(function(d) { - return d.value; - })); - job_detail_chart = nv.models.pieChart() - .margin({bottom: 15}) - .x(function(d) { - return d.label +': '+ Math.floor((d.value/total)*100) + "%"; - }) - .y(function(d) { return d.value; }) - .showLabels(false) - .showLegend(true) - .growOnHover(false) - .labelThreshold(0.01) - .tooltipContent(function(x, y) { - return '

'+x+'

'+ '

' + Math.floor(y.replace(',','')) + ' HOSTS ' + '

'; - }) - .color(colors); - job_detail_chart.legend.rightAlign(false); - job_detail_chart.legend.margin({top: 5, right: 450, left:0, bottom: 0}); - d3.select(element.find('svg')[0]) - .datum(dataset) - .transition().duration(350) - .call(job_detail_chart) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/assets/OpenSans-Regular.ttf)", - "width": 600, - "height": 300, - "color": '#848992' - }); - d3.select(element.find(".nv-noData")[0]) - .style({ - "text-anchor": 'start' - }); - return job_detail_chart; - }; - }]) - - - .factory('DrawPlays', [function() { - return function(params) { - var scope = params.scope, - idx = 0, - result = [], - newKeys = [], - //plays = JSON.parse(JSON.stringify(scope.jobData.plays)), - plays = scope.jobData.plays, - filteredListX = [], - filteredListB = [], - key, - keys; - - function listSort(a,b) { - if (parseInt(a,10) < parseInt(b,10)) { - return -1; - } - if (parseInt(a,10) > parseInt(b,10)) { - return 1; - } - return 0; - } - - // Only draw plays that are in the 'active' list - for (key in plays) { - if (plays[key].taskCount > 0) { - filteredListX[key] = plays[key]; - } - } - - keys = Object.keys(filteredListB); - keys.sort(function(a,b) { return listSort(a,b); }).reverse(); - for (idx=0; idx < scope.playsMaxRows && idx < keys.length; idx++) { - newKeys.push(keys[idx]); - } - newKeys.sort(function(a,b) { return listSort(a,b); }); - idx = 0; - while (idx < newKeys.length) { - result.push(filteredListB[newKeys[idx]]); - idx++; - } - setTimeout( function() { - scope.$apply( function() { - scope.plays = result; - scope.selectedPlay = scope.activePlay; - if (scope.liveEventProcessing) { - $('#plays-table-detail').scrollTop($('#plays-table-detail').prop("scrollHeight")); - } - }); - }); - }; - }]) - - .factory('DrawTasks', [ function() { - return function(params) { - var scope = params.scope, - result = [], - filteredListX = [], - filteredListB = [], - idx, key, keys, newKeys, tasks, t; - - function listSort(a,b) { - if (parseInt(a,10) < parseInt(b,10)) { - return -1; - } - if (parseInt(a,10) > parseInt(b,10)) { - return 1; - } - return 0; - } - - if (scope.activePlay && scope.jobData.plays[scope.activePlay]) { - - //tasks = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks)); - tasks = scope.jobData.plays[scope.activePlay].tasks; - - // Only draw tasks that are in the 'active' list - for (key in tasks) { - t = tasks[key]; - if (t.reportedHosts > 0 || t.hostCount > 0 || t.successfulCount >0 || t.failedCount > 0 || - t.changedCount > 0 || t.skippedCount > 0 || t.unreachableCount > 0) { - filteredListX[key] = tasks[key]; - } - } - - keys = Object.keys(filteredListB); - keys.sort(function(a,b) { return listSort(a,b); }).reverse(); - newKeys = []; - for (idx=0; result.length < scope.tasksMaxRows && idx < keys.length; idx++) { - newKeys.push(keys[idx]); - } - newKeys.sort(function(a,b) { return listSort(a,b); }); - idx = 0; - while (idx < newKeys.length) { - result.push(filteredListB[newKeys[idx]]); - idx++; - } - } - - setTimeout( function() { - scope.$apply( function() { - scope.tasks = result; - scope.selectedTask = scope.activeTask; - if (scope.liveEventProcessing) { - $('#tasks-table-detail').scrollTop($('#tasks-table-detail').prop("scrollHeight")); - } - }); - }); - - }; - }]) - - .factory('DrawHostResults', [ function() { - return function(params) { - var scope = params.scope, - result = [], - filteredListB = [], - idx = 0, - hostResults, - keys; - - if (scope.activePlay && scope.activeTask && scope.jobData.plays[scope.activePlay] && - scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) { - - hostResults = scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults; - - keys = Object.keys(filteredListB); - keys.sort(function compare(a, b) { - if (filteredListB[a].name === filteredListB[b].name) { - if (filteredListB[a].counter < filteredListB[b].counter) { - return -1; - } - if (filteredListB[a].counter >filteredListB[b].counter) { - return 1; - } - } else { - if (filteredListB[a].name < filteredListB[b].name) { - return -1; - } - if (filteredListB[a].name > filteredListB[b].name) { - return 1; - } - } - // a must be equal to b - return 0; - }); - while (idx < keys.length && result.length < scope.hostResultsMaxRows) { - result.push(filteredListB[keys[idx]]); - idx++; - } - } - - setTimeout( function() { - scope.$apply( function() { - scope.hostResults = result; - if (scope.liveEventProcessing) { - $('#hosts-table-detail').scrollTop($('#hosts-table-detail').prop("scrollHeight")); - } - }); - }); - - }; - }]) - - .factory('UpdateDOM', ['DrawPlays', 'DrawTasks', 'DrawHostResults', - function(DrawPlays, DrawTasks, DrawHostResults) { - return function(params) { - var scope = params.scope; - if (!scope.pauseLiveEvents) { - DrawPlays({ scope: scope }); - DrawTasks({ scope: scope }); - DrawHostResults({ scope: scope }); - } - - setTimeout(function() { - scope.playsLoading = false; - scope.tasksLoading = false; - scope.hostResultsLoading = false; - },100); - }; - }]); diff --git a/awx/ui/client/src/job-detail/host-events/host-events.block.less b/awx/ui/client/src/job-detail/host-events/host-events.block.less deleted file mode 100644 index df52944cf0..0000000000 --- a/awx/ui/client/src/job-detail/host-events/host-events.block.less +++ /dev/null @@ -1,93 +0,0 @@ -@import "./client/src/shared/branding/colors.less"; -@import "./client/src/shared/branding/colors.default.less"; - -.HostEvents .CodeMirror{ - border: none; -} -.HostEvents .modal-footer{ - border: 0; - margin-top: 0px; - padding: 0px 20px 20px 20px; -} -button.HostEvents-close{ - width: 70px; - color: #FFFFFF!important; - text-transform: uppercase; - padding-left: 15px; - padding-right: 15px; - background-color: @default-link; - border-color: @default-link; - &:hover{ - background-color: @default-link-hov; - border-color: @default-link-hov; - } -} -.HostEvents-status--ok{ - color: @green; -} -.HostEvents-status--unreachable{ - color: @unreachable; -} -.HostEvents-status--changed{ - color: @changed; -} -.HostEvents-status--failed{ - color: @default-err; -} -.HostEvents-status--skipped{ - color: @skipped; -} - -.HostEvents-filter--form{ - padding-top: 15px; - padding-bottom: 15px; - float: right; - display: inline-block; -} -.HostEvents .modal-body{ - padding: 20px; -} -.HostEvents .select2-container{ - text-transform: capitalize; - max-width: 220px; - float: right; -} -.HostEvents-form--container{ - padding-top: 15px; - padding-bottom: 15px; -} -.HostEvents-title{ - text-transform: uppercase; - color: @default-interface-txt; - font-weight: 600; -} -.HostEvents-status i { - padding-right: 10px; -} -.HostEvents-table--header { - height: 30px; - font-size: 14px; - font-weight: normal; - text-transform: uppercase; - color: @default-interface-txt; - background-color: @default-list-header-bg; - padding-left: 15px; - padding-right: 15px; - border-bottom-width: 0px; -} -.HostEvents-table--header:first-of-type{ - border-top-left-radius: 5px; -} -.HostEvents-table--header:last-of-type{ - border-top-right-radius: 5px; -} -.HostEvents-table--row{ - color: @default-data-txt; - border: 0 !important; -} -.HostEvents-table--row:nth-child(odd){ - background: @default-tertiary-bg; -} -.HostEvents-table--cell{ - border: 0 !important; -} diff --git a/awx/ui/client/src/job-detail/host-events/host-events.controller.js b/awx/ui/client/src/job-detail/host-events/host-events.controller.js deleted file mode 100644 index 5664c89877..0000000000 --- a/awx/ui/client/src/job-detail/host-events/host-events.controller.js +++ /dev/null @@ -1,48 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$stateParams', '$scope', '$rootScope', '$state', 'Wait', - 'JobDetailService', 'CreateSelect2', 'hosts', - function($stateParams, $scope, $rootScope, $state, Wait, - JobDetailService, CreateSelect2, hosts){ - - // pagination not implemented yet, but it'll depend on this - $scope.page_size = $stateParams.page_size; - - $scope.processEventStatus = JobDetailService.processEventStatus; - $scope.activeFilter = $stateParams.filter || null; - - $scope.filters = ['all', 'changed', 'failed', 'ok', 'unreachable', 'skipped']; - - // watch select2 for changes - $('.HostEvents-select').on("select2:select", function () { - $scope.activeFilter = $('.HostEvents-select').val(); - }); - - var init = function(){ - $scope.hostName = $stateParams.hostName; - // create filter dropdown - CreateSelect2({ - element: '.HostEvents-select', - multiple: false - }); - // process the filter if one was passed - if ($stateParams.filter){ - $scope.activeFilter = $stateParams.filter; - - $('#HostEvents').modal('show'); - } - else{ - $scope.results = hosts.data.results; - $('#HostEvents').modal('show'); - } - }; - - - init(); - - }]; diff --git a/awx/ui/client/src/job-detail/host-events/host-events.partial.html b/awx/ui/client/src/job-detail/host-events/host-events.partial.html deleted file mode 100644 index 00bacf066c..0000000000 --- a/awx/ui/client/src/job-detail/host-events/host-events.partial.html +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js deleted file mode 100644 index cd5a241622..0000000000 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js +++ /dev/null @@ -1,131 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$scope', '$rootScope', '$stateParams', 'Wait', 'JobDetailService', 'DrawGraph', function($scope, $rootScope, $stateParams, Wait, JobDetailService, DrawGraph){ - - var page_size = 200; - $scope.loading = $scope.hosts.length > 0 ? false : true; - $scope.filter = 'all'; - - var buildGraph = function(hosts){ - // status waterfall: unreachable > failed > changed > ok > skipped - var count; - count = { - ok : _.filter(hosts, function(o){ - return o.failures === 0 && o.changed === 0 && o.ok > 0; - }), - skipped : _.filter(hosts, function(o){ - return o.skipped > 0; - }), - unreachable : _.filter(hosts, function(o){ - return o.dark > 0; - }), - failures : _.filter(hosts, function(o){ - return o.failed === true; - }), - changed : _.filter(hosts, function(o){ - return o.changed > 0; - }) - }; - return count; - }; - var init = function(){ - Wait('start'); - JobDetailService.getJobHostSummaries($stateParams.id, {page_size: page_size, order_by: 'host_name'}) - .success(function(res){ - $scope.hosts = res.results; - $scope.next = res.next; - $scope.count = buildGraph(res.results); - Wait('stop'); - DrawGraph({count: $scope.count, resize:true}); - }); - JobDetailService.getJob({id: $stateParams.id}) - .success(function(res){ - $scope.status = res.results[0].status; - }); - }; - if ($rootScope.removeJobSummaryComplete) { - $rootScope.removeJobSummaryComplete(); - } - // emitted by the API in the same function used to persist host summary data - // JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py - $scope.$on('ws-jobs-summary', function(e, data) { - // discard socket msgs we don't care about in this context - if (parseInt($stateParams.id) === data.unified_job_id){ - init(); - } - }); - - $scope.$on('ws-jobs', function(e, data) { - if (parseInt($stateParams.id) === data.unified_job_id){ - $scope.status = data.status; - } - }); - - - $scope.buildTooltip = function(n, status){ - var grammar = function(n, status){ - var dict = { - 0: 'No host events were ', - 1: ' host event was ', - 2: ' host events were ' - }; - if (n >= 2){ - return n + dict[2] + status; - } - else{ - return n !== 0 ? n + dict[n] + status : dict[n] + status; - } - }; - return grammar(n, status); - }; - $scope.getNextPage = function(){ - if ($scope.next){ - JobDetailService.getNextPage($scope.next).success(function(res){ - res.results.forEach(function(key, index){ - $scope.hosts.push(res.results[index]); - }); - $scope.hosts.push(res.results); - $scope.next = res.next; - }); - } - }; - - $scope.setFilter = function(filter){ - $scope.filter = filter; - var getAll = function(){ - Wait('start'); - JobDetailService.getJobHostSummaries($stateParams.id, { - page_size: page_size, - order_by: 'host_name' - }).success(function(res){ - Wait('stop'); - $scope.hosts = res.results; - $scope.next = res.next; - }); - }; - var getFailed = function(){ - Wait('start'); - JobDetailService.getJobHostSummaries($stateParams.id, { - page_size: page_size, - failed: true, - order_by: 'host_name' - }).success(function(res){ - Wait('stop'); - $scope.hosts = res.results; - $scope.next = res.next; - }); - }; - $scope.get = filter === 'all' ? getAll() : getFailed(); - }; - - init(); - // calling the init routine twice will size the d3 chart correctly - no idea why - // instantiating the graph inside a setTimeout() SHOULD have the same effect, but it doesn't - // instantiating the graph further down the promise chain e.g. .then() or .finally() also does not work - init(); - }]; diff --git a/awx/ui/client/src/job-detail/job-detail.controller.js b/awx/ui/client/src/job-detail/job-detail.controller.js deleted file mode 100644 index c3479e397b..0000000000 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ /dev/null @@ -1,1027 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:JobDetail - * @description This controller's for the Job Detail Page -*/ - -export default - [ '$location', '$rootScope', '$filter', '$scope', '$compile', '$state', '$stateParams', '$log', 'ClearScope', - 'GetBasePath', 'Wait', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'GetElapsed', 'JobIsFinished', - 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'RelaunchPlaybook', 'LoadPlays', 'LoadTasks', - 'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels', 'EditSchedule', - 'ParseTypeChange', 'JobDetailService', - function( - $location, $rootScope, $filter, $scope, $compile, $state, $stateParams, $log, ClearScope, - GetBasePath, Wait, ProcessErrors, SelectPlay, SelectTask, GetElapsed, JobIsFinished, - SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob, RelaunchPlaybook, LoadPlays, LoadTasks, - ParseVariableString, GetChoices, fieldChoices, fieldLabels, EditSchedule, - ParseTypeChange, JobDetailService - ) { - ClearScope(); - - var job_id = $stateParams.id, - scope = $scope, - api_complete = false, - refresh_count = 0, - lastEventId = 0, - verbosity_options, - job_type_options; - - scope.plays = []; - scope.parseType = 'yaml'; - scope.previousTaskFailed = false; - $scope.stdoutFullScreen = false; - - scope.$watch('job_status', function(job_status) { - if (job_status && job_status.explanation && job_status.explanation.split(":")[0] === "Previous Task Failed") { - scope.previousTaskFailed = true; - var taskObj = JSON.parse(job_status.explanation.substring(job_status.explanation.split(":")[0].length + 1)); - // return a promise from the options request with the permission type choices (including adhoc) as a param - var fieldChoice = fieldChoices({ - scope: $scope, - url: 'api/v1/unified_jobs/', - field: 'type' - }); - - // manipulate the choices from the options request to be set on - // scope and be usable by the list form - fieldChoice.then(function (choices) { - choices = - fieldLabels({ - choices: choices - }); - scope.explanation_fail_type = choices[taskObj.job_type]; - scope.explanation_fail_name = taskObj.job_name; - scope.explanation_fail_id = taskObj.job_id; - scope.task_detail = scope.explanation_fail_type + " failed for " + scope.explanation_fail_name + " with ID " + scope.explanation_fail_id + "."; - }); - } else { - scope.previousTaskFailed = false; - } - }, true); - - scope.$watch('plays', function(plays) { - for (var play in plays) { - if (plays[play].elapsed) { - plays[play].finishedTip = "Play completed at " + $filter("longDate")(plays[play].finished) + "."; - } else { - plays[play].finishedTip = "Play not completed."; - } - } - }); - scope.hosts = []; - scope.tasks = []; - scope.$watch('tasks', function(tasks) { - for (var task in tasks) { - if (tasks[task].elapsed) { - tasks[task].finishedTip = "Task completed at " + $filter("longDate")(tasks[task].finished) + "."; - } else { - tasks[task].finishedTip = "Task not completed."; - } - if (tasks[task].successfulCount) { - tasks[task].successfulCountTip = tasks[task].successfulCount; - tasks[task].successfulCountTip += (tasks[task].successfulCount === 1) ? " host event was" : " host events were"; - tasks[task].successfulCountTip += " ok."; - } else { - tasks[task].successfulCountTip = "No host events were ok."; - } - if (tasks[task].changedCount) { - tasks[task].changedCountTip = tasks[task].changedCount; - tasks[task].changedCountTip += (tasks[task].changedCount === 1) ? " host event" : " host events"; - tasks[task].changedCountTip += " changed."; - } else { - tasks[task].changedCountTip = "No host events changed."; - } - if (tasks[task].skippedCount) { - tasks[task].skippedCountTip = tasks[task].skippedCount; - tasks[task].skippedCountTip += (tasks[task].skippedCount === 1) ? " host event was" : " hosts events were"; - tasks[task].skippedCountTip += " skipped."; - } else { - tasks[task].skippedCountTip = "No host events were skipped."; - } - if (tasks[task].failedCount) { - tasks[task].failedCountTip = tasks[task].failedCount; - tasks[task].failedCountTip += (tasks[task].failedCount === 1) ? " host event" : " host events"; - tasks[task].failedCountTip += " failed."; - } else { - tasks[task].failedCountTip = "No host events failed."; - } - if (tasks[task].unreachableCount) { - tasks[task].unreachableCountTip = tasks[task].unreachableCount; - tasks[task].unreachableCountTip += (tasks[task].unreachableCount === 1) ? " host event was" : " hosts events were"; - tasks[task].unreachableCountTip += " unreachable."; - } else { - tasks[task].unreachableCountTip = "No host events were unreachable."; - } - if (tasks[task].missingCount) { - tasks[task].missingCountTip = tasks[task].missingCount; - tasks[task].missingCountTip += (tasks[task].missingCount === 1) ? " host event was" : " host events were"; - tasks[task].missingCountTip += " missing."; - } else { - tasks[task].missingCountTip = "No host events were missing."; - } - } - }); - scope.hostResults = []; - - scope.hostResultsMaxRows = 200; - scope.tasksMaxRows = 200; - scope.playsMaxRows = 200; - - // Set the following to true when 'Loading...' message desired - scope.playsLoading = true; - scope.tasksLoading = true; - scope.hostResultsLoading = true; - - // Turn on the 'Waiting...' message until events begin arriving - scope.waiting = true; - - scope.liveEventProcessing = true; // true while job is active and live events are arriving - scope.pauseLiveEvents = false; // control play/pause state of event processing - - scope.job_status = {}; - scope.job_id = job_id; - scope.auto_scroll = false; - - scope.haltEventQueue = false; - scope.processing = false; - scope.lessStatus = false; - scope.lessDetail = false; - // pops the event summary panel open if we're in the host summary child state - //scope.lessEvents = ($state.current.name === 'jobDetail.host-summary' || $state.current.name === 'jobDetail.host-events') ? false : true; - if ($state.current.name === 'jobDetail.host-summary' ){ - scope.lessEvents = false; - } - else{ - scope.lessEvents = true; - } - scope.jobData = {}; - scope.jobData.hostSummaries = {}; - - verbosity_options = [ - { value: 0, label: 'Default' }, - { value: 1, label: 'Verbose' }, - { value: 3, label: 'Debug' } - ]; - - job_type_options = [ - { value: 'run', label: 'Run' }, - { value: 'check', label: 'Check' } - ]; - - GetChoices({ - scope: scope, - url: GetBasePath('unified_jobs'), - field: 'status', - variable: 'status_choices', - }); - - scope.eventsHelpText = "

Successful

\n" + - "

Changed

\n" + - "

Unreachable

\n" + - "

Failed

\n"; - - scope.$on(`ws-job_events-${job_id}`, function(e, data) { - // update elapsed time on each event received - scope.job_status.elapsed = GetElapsed({ - start: scope.job.created, - end: Date.now() - }); - if (api_complete && data.id > lastEventId) { - scope.waiting = false; - data.event = data.event_name; - DigestEvent({ scope: scope, event: data }); - } - UpdateDOM({ scope: scope }); - }); - - scope.$on(`ws-jobs`, function(e, data) { - // if we receive a status change event for the current job indicating the job - // is finished, stop event queue processing and reload - if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { - if (data.status === 'failed' || data.status === 'canceled' || - data.status === 'error' || data.status === 'successful' || data.status === 'running') { - $scope.liveEventProcessing = false; - if (!scope.pauseLiveEvents) { - $scope.$emit('LoadJob'); //this is what is used for the refresh - } - } - } - }); - - scope.$on('ws-jobs-summary', function() { - // the job host summary should now be available from the API - $log.debug('Trigging reload of job_host_summaries'); - scope.$emit('InitialLoadComplete'); - }); - - if (scope.removeInitialLoadComplete) { - scope.removeInitialLoadComplete(); - } - scope.removeInitialLoadComplete = scope.$on('InitialLoadComplete', function() { - Wait('stop'); - - if (JobIsFinished(scope)) { - scope.liveEventProcessing = false; // signal that event processing is over and endless scroll - scope.pauseLiveEvents = false; // should be enabled - var params = { - event: 'playbook_on_stats' - }; - JobDetailService.getRelatedJobEvents(scope.job.id, params) - .success(function() { - UpdateDOM({ scope: scope }); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call failed. GET returned: ' + status }); - }); - $log.debug('Job completed!'); - $log.debug(scope.jobData); - } - else { - api_complete = true; //trigger events to start processing - UpdateDOM({ scope: scope}); - } - }); - - if (scope.removeLoadHosts) { - scope.removeLoadHosts(); - } - scope.removeLoadHosts = scope.$on('LoadHosts', function() { - if (scope.activeTask) { - - var play = scope.jobData.plays[scope.activePlay], - task; - if(play){ - task = play.tasks[scope.activeTask]; - } - if (play && task) { - var params = { - parent: task.id, - event__startswith: 'runner', - page_size: scope.hostResultsMaxRows - }; - JobDetailService.getRelatedJobEvents(scope.job.id, params) - .success(function(data) { - if (data.results.length > 0) { - lastEventId = data.results[0].id; - } - scope.next_host_results = data.next; - task.hostResults = JobDetailService.processHostEvents(data.results); - scope.$emit('InitialLoadComplete'); - }); - } else { - scope.$emit('InitialLoadComplete'); - } - } else { - scope.$emit('InitialLoadComplete'); - } - }); - - if (scope.removeLoadTasks) { - scope.removeLoadTasks(); - } - scope.removeLoadTasks = scope.$on('LoadTasks', function() { - if (scope.activePlay) { - var play = scope.jobData.plays[scope.activePlay]; - - if (play) { - var params = { - event_id: play.id, - page_size: scope.tasksMaxRows, - order: 'id' - }; - JobDetailService.getJobTasks(scope.job.id, params) - .success(function(data) { - scope.next_tasks = data.next; - if (data.results.length > 0) { - lastEventId = data.results[data.results.length - 1].id; - if (scope.liveEventProcessing) { - scope.activeTask = data.results[data.results.length - 1].id; - } - else { - scope.activeTask = data.results[0].id; - } - scope.selectedTask = scope.activeTask; - } - data.results.forEach(function(event, idx) { - var end, elapsed, status, status_text; - - if (play.firstTask === undefined || play.firstTask === null) { - play.firstTask = event.id; - play.hostCount = (event.host_count) ? event.host_count : 0; - } - - if (idx < data.results.length - 1) { - // end date = starting date of the next event - end = data.results[idx + 1].created; - } - else { - // no next event (task), get the end time of the play - if(scope.jobData.plays[scope.activePlay]){ - end = scope.jobData.plays[scope.activePlay].finished; - } - } - - if (end) { - elapsed = GetElapsed({ - start: event.created, - end: end - }); - } - else { - elapsed = '00:00:00'; - } - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - - play.tasks[event.id] = { - id: event.id, - play_id: scope.activePlay, - name: event.name, - status: status, - status_text: status_text, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - created: event.created, - modified: event.modified, - finished: end, - elapsed: elapsed, - hostCount: (event.host_count) ? event.host_count : 0, - reportedHosts: (event.reported_hosts) ? event.reported_hosts : 0, - successfulCount: (event.successful_count) ? event.successful_count : 0, - failedCount: (event.failed_count) ? event.failed_count : 0, - changedCount: (event.changed_count) ? event.changed_count : 0, - skippedCount: (event.skipped_count) ? event.skipped_count : 0, - unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0, - taskActiveClass: '', - hostResults: {} - }; - if (play.firstTask !== event.id) { - // this is not the first task - play.tasks[event.id].hostCount = play.tasks[play.firstTask].hostCount; - } - if (play.tasks[event.id].reportedHosts === 0 && play.tasks[event.id].successfulCount === 0 && - play.tasks[event.id].failedCount === 0 && play.tasks[event.id].changedCount === 0 && - play.tasks[event.id].skippedCount === 0 && play.tasks[event.id].unreachableCount === 0) { - play.tasks[event.id].status = 'no-matching-hosts'; - play.tasks[event.id].status_text = 'No matching hosts'; - play.tasks[event.id].status_tip = "Event ID: " + event.id + "
Status: No matching hosts"; - } - play.taskCount++; - SetTaskStyles({ - task: play.tasks[event.id] - }); - }); - if (scope.activeTask && scope.jobData.plays[scope.activePlay] && scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) { - scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = 'JobDetail-tableRow--selected'; - } - scope.$emit('LoadHosts'); - }) - .error(function(data) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call failed. GET returned: ' + status }); - }); - } else { - scope.$emit('InitialLoadComplete'); - } - } else { - scope.$emit('InitialLoadComplete'); - } - }); - - if (scope.removeLoadPlays) { - scope.removeLoadPlays(); - } - scope.removeLoadPlays = scope.$on('LoadPlays', function(e, events_url) { - scope.jobData.plays = {}; - var params = { - order_by: 'id' - }; - - JobDetailService.getJobPlays(scope.job.id, params) - .success( function(data) { - scope.next_plays = data.next; - if (data.results.length > 0) { - lastEventId = data.results[data.results.length - 1].id; - if (scope.liveEventProcessing) { - scope.activePlay = data.results[data.results.length - 1].id; - } - else { - scope.activePlay = data.results[0].id; - } - scope.selectedPlay = scope.activePlay; - } else { - // if we are here, there are no plays and the job has failed, let the user know they may want to consult stdout - if ( (scope.job_status.status === 'failed' || scope.job_status.status === 'error') && - (!scope.job_status.explanation)) { - scope.job_status.explanation = "See standard out for more details"; - } - } - data.results.forEach(function(event, idx) { - var status, status_text, start, end, elapsed, ok, changed, failed, skipped; - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - start = event.started; - - if (idx < data.results.length - 1) { - // end date = starting date of the next event - end = data.results[idx + 1].started; - } - else if (JobIsFinished(scope)) { - // this is the last play and the job already finished - end = scope.job_status.finished; - } - if (end) { - elapsed = GetElapsed({ - start: start, - end: end - }); - } - else { - elapsed = '00:00:00'; - } - - scope.jobData.plays[event.id] = { - id: event.id, - name: event.play, - created: start, - finished: end, - status: status, - status_text: status_text, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - elapsed: elapsed, - hostCount: 0, - fistTask: null, - taskCount: 0, - playActiveClass: '', - unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0, - tasks: {} - }; - - ok = (event.ok_count) ? event.ok_count : 0; - changed = (event.changed_count) ? event.changed_count : 0; - failed = (event.failed_count) ? event.failed_count : 0; - skipped = (event.skipped_count) ? event.skipped_count : 0; - - scope.jobData.plays[event.id].hostCount = ok + changed + failed + skipped; - - if (scope.jobData.plays[event.id].hostCount > 0 || event.unreachable_count > 0 || scope.job_status.status === 'successful' || - scope.job_status.status === 'failed' || scope.job_status.status === 'error' || scope.job_status.status === 'canceled') { - // force the play to be on the 'active' list - scope.jobData.plays[event.id].taskCount = 1; - } - - if (scope.jobData.plays[event.id].hostCount === 0 && event.unreachable_count === 0) { - scope.jobData.plays[event.id].status = 'no-matching-hosts'; - scope.jobData.plays[event.id].status_text = 'No matching hosts'; - scope.jobData.plays[event.id].status_tip = "Event ID: " + event.id + "
Status: No matching hosts"; - } - }); - if (scope.activePlay && scope.jobData.plays[scope.activePlay]) { - scope.jobData.plays[scope.activePlay].playActiveClass = 'JobDetail-tableRow--selected'; - } - scope.$emit('LoadTasks', events_url); - }); - }); - - - if (scope.removeLoadJob) { - scope.removeLoadJob(); - } - scope.removeLoadJobRow = scope.$on('LoadJob', function() { - Wait('start'); - scope.job_status = {}; - - scope.playsLoading = true; - scope.tasksLoading = true; - scope.hostResultsLoading = true; - - // Load the job record - JobDetailService.getJob({id: job_id}) - .success(function(res) { - var i, - data = res.results[0]; - scope.job = data; - scope.job_template_name = data.name; - scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : ''; - scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : ''; - scope.job_template_url = '/#/templates/' + data.unified_job_template; - scope.inventory_url = (scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : ''; - scope.project_url = (scope.project_name && data.project) ? '/#/projects/' + data.project : ''; - scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : ''; - scope.cloud_credential_url = (data.cloud_credential) ? '/#/credentials/' + data.cloud_credential : ''; - scope.playbook = data.playbook; - scope.credential = data.credential; - scope.cloud_credential = data.cloud_credential; - scope.forks = data.forks; - scope.limit = data.limit; - scope.verbosity = data.verbosity; - scope.job_tags = data.job_tags; - scope.variables = ParseVariableString(data.extra_vars); - - // If we get created_by back from the server then use it. This means that the job was kicked - // off by a user and not a schedule AND that the user still exists in the system. - if(data.summary_fields.created_by) { - scope.users_url = '/#/users/' + data.summary_fields.created_by.id; - scope.created_by = data.summary_fields.created_by.username; - } - else { - if(data.summary_fields.schedule) { - // Build the Launched By link to point to the schedule that kicked it off - scope.scheduled_by = (data.summary_fields.schedule.name) ? data.summary_fields.schedule.name.toString() : ''; - } - // If there is no schedule or created_by then we can assume that the job was - // created by a deleted user - } - - if (data.summary_fields.credential) { - scope.credential_name = data.summary_fields.credential.name; - scope.credential_url = data.related.credential - .replace('api/v1', '#'); - } else { - scope.credential_name = ""; - } - - if (data.summary_fields.cloud_credential) { - scope.cloud_credential_name = data.summary_fields.cloud_credential.name; - scope.cloud_credential_url = data.related.cloud_credential - .replace('api/v1', '#'); - } else { - scope.cloud_credential_name = ""; - } - - if (data.summary_fields.network_credential) { - scope.network_credential_name = data.summary_fields.network_credential.name; - scope.network_credential_url = data.related.network_credential - .replace('api/v1', '#'); - } else { - scope.network_credential_name = ""; - } - - for (i=0; i < verbosity_options.length; i++) { - if (verbosity_options[i].value === data.verbosity) { - scope.verbosity = verbosity_options[i].label; - } - } - - for (i=0; i < job_type_options.length; i++) { - if (job_type_options[i].value === data.job_type) { - scope.job_type = job_type_options[i].label; - } - } - - // In the case the job is already completed, or an error already happened, - // populate scope.job_status info - scope.job_status.status = (data.status === 'waiting' || data.status === 'new') ? 'pending' : data.status; - scope.job_status.started = data.started; - scope.job_status.status_class = ((data.status === 'error' || data.status === 'failed') && data.job_explanation) ? "alert alert-danger" : ""; - scope.job_status.explanation = data.job_explanation; - if(data.result_traceback) { - scope.job_status.traceback = data.result_traceback.trim().split('\n').join('
'); - } - if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') { - scope.job_status.finished = data.finished; - scope.liveEventProcessing = false; - scope.pauseLiveEvents = false; - scope.waiting = false; - scope.playsLoading = false; - scope.tasksLoading = false; - scope.hostResultsLoading = false; - } - else { - scope.job_status.finished = null; - } - - if (data.started && data.finished) { - scope.job_status.elapsed = GetElapsed({ - start: data.started, - end: data.finished - }); - } - else { - scope.job_status.elapsed = '00:00:00'; - } - scope.status_choices.every(function(status) { - if (status.value === scope.job.status) { - scope.job_status.status_label = status.label; - return false; - } - return true; - }); - //scope.setSearchAll('host'); - ParseTypeChange({ scope: scope, field_id: 'pre-formatted-variables', readOnly: true }); - scope.$emit('LoadPlays', data.related.job_events); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve job: ' + $stateParams.id + '. GET returned: ' + status }); - }); - }); - - - if (scope.removeRefreshCompleted) { - scope.removeRefreshCompleted(); - } - scope.removeRefreshCompleted = scope.$on('RefreshCompleted', function() { - refresh_count++; - if (refresh_count === 1) { - // First time. User just loaded page. - scope.$emit('LoadJob'); - } - }); - - scope.adjustSize = function() { - var height, ww = $(window).width(); - if (ww < 1024) { - $('#job-summary-container').hide(); - $('#job-detail-container').css({ "width": "100%", "padding-right": "15px" }); - $('#summary-button').show(); - } - else { - $('.overlay').hide(); - $('#summary-button').hide(); - $('#hide-summary-button').hide(); - $('#job-summary-container .job_well').css({ - 'box-shadow': 'none', - 'height': 'auto' - }); - $('#job-summary-container').css({ - "width": "41.66666667%", - "padding-left": "7px", - "padding-right": "15px", - "z-index": 0 - }); - setTimeout(function() { $('#job-summary-container .job_well').height($('#job-detail-container').height() - 18); }, 500); - $('#job-summary-container').show(); - } - - scope.lessStatus = false; // close the view more status option - - - height = $(window).height() - $('#main-menu-container .navbar').outerHeight() - - $('#job-detail-container').outerHeight() - 20; - scope.$emit('RefreshCompleted'); - }; - - setTimeout(function() { scope.adjustSize(); }, 500); - - // Use debounce for the underscore library to adjust after user resizes window. - $(window).resize(_.debounce(function(){ - scope.adjustSize(); - }, 500)); - - function flashPlayTip() { - setTimeout(function(){ - $('#play-help').popover('show'); - },500); - setTimeout(function() { - $('#play-help').popover('hide'); - }, 5000); - } - - scope.selectPlay = function(id) { - if (scope.liveEventProcessing && !scope.pauseLiveEvents) { - scope.pauseLiveEvents = true; - flashPlayTip(); - } - SelectPlay({ - scope: scope, - id: id - }); - }; - - scope.selectTask = function(id) { - if (scope.liveEventProcessing && !scope.pauseLiveEvents) { - scope.pauseLiveEvents = true; - flashPlayTip(); - } - SelectTask({ - scope: scope, - id: id - }); - }; - - scope.togglePlayButton = function() { - if (scope.pauseLiveEvents) { - scope.pauseLiveEvents = false; - scope.$emit('LoadJob'); - } - }; - - scope.objectIsEmpty = function(obj) { - if (angular.isObject(obj)) { - return (Object.keys(obj).length > 0) ? false : true; - } - return true; - }; - - scope.toggleLessEvents = function() { - if (!scope.lessEvents) { - $('#events-summary').slideUp(0); - scope.lessEvents = true; - } - else { - $('#events-summary').slideDown(0); - scope.lessEvents = false; - } - }; - - scope.toggleLessStatus = function() { - if (!scope.lessStatus) { - $('#job-status-form').slideUp(200); - scope.lessStatus = true; - } - else { - $('#job-status-form').slideDown(200); - scope.lessStatus = false; - } - }; - - scope.toggleLessDetail = function() { - if (!scope.lessDetail) { - $('#job-detail-details').slideUp(200); - scope.lessDetail = true; - } - else { - $('#job-detail-details').slideDown(200); - scope.lessDetail = false; - } - }; - - if (scope.removeDeleteFinished) { - scope.removeDeleteFinished(); - } - scope.removeDeleteFinished = scope.$on('DeleteFinished', function(e, action) { - Wait('stop'); - if (action !== 'cancel') { - Wait('stop'); - $location.url('/jobs'); - } - }); - - scope.deleteJob = function() { - DeleteJob({ - scope: scope, - id: scope.job.id, - job: scope.job, - callback: 'DeleteFinished' - }); - }; - - scope.relaunchJob = function() { - RelaunchPlaybook({ - scope: scope, - id: scope.job.id - }); - }; - - scope.playsScrollDown = function() { - // check for more plays when user scrolls to bottom of play list... - if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_plays) { - $('#playsMoreRows').fadeIn(); - scope.playsLoading = true; - JobDetailService.getNextPage(scope.next_plays) - .success( function(data) { - scope.next_plays = data.next; - data.results.forEach(function(event, idx) { - var status, status_text, start, end, elapsed, ok, changed, failed, skipped; - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - start = event.started; - - if (idx < data.results.length - 1) { - // end date = starting date of the next event - end = data.results[idx + 1].started; - } - else if (JobIsFinished(scope)) { - // this is the last play and the job already finished - end = scope.job_status.finished; - } - if (end) { - elapsed = GetElapsed({ - start: start, - end: end - }); - } - else { - elapsed = '00:00:00'; - } - - scope.plays.push({ - id: event.id, - name: event.play, - created: start, - finished: end, - status: status, - status_text: status_text, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - elapsed: elapsed, - hostCount: 0, - fistTask: null, - playActiveClass: '', - unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0, - }); - - ok = (event.ok_count) ? event.ok_count : 0; - changed = (event.changed_count) ? event.changed_count : 0; - failed = (event.failed_count) ? event.failed_count : 0; - skipped = (event.skipped_count) ? event.skipped_count : 0; - - scope.plays[scope.plays.length - 1].hostCount = ok + changed + failed + skipped; - scope.playsLoading = false; - }); - $('#playsMoreRows').fadeOut(400); - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + scope.next_plays + '. GET returned: ' + status }); - }); - } - }; - - scope.tasksScrollDown = function() { - // check for more tasks when user scrolls to bottom of task list... - if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_tasks) { - $('#tasksMoreRows').fadeIn(); - scope.tasksLoading = true; - JobDetailService.getNextPage(scope.next_tasks) - .success(function(data) { - scope.next_tasks = data.next; - data.results.forEach(function(event, idx) { - var end, elapsed, status, status_text; - if (idx < data.results.length - 1) { - // end date = starting date of the next event - end = data.results[idx + 1].created; - } - else { - // no next event (task), get the end time of the play - scope.plays.every(function(p, j) { - if (p.id === scope.selectedPlay) { - end = scope.plays[j].finished; - return false; - } - return true; - }); - } - if (end) { - elapsed = GetElapsed({ - start: event.created, - end: end - }); - } - else { - elapsed = '00:00:00'; - } - - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - - scope.tasks.push({ - id: event.id, - play_id: scope.selectedPlay, - name: event.name, - status: status, - status_text: status_text, - status_tip: "Event ID: " + event.id + "
Status: " + status_text, - created: event.created, - modified: event.modified, - finished: end, - elapsed: elapsed, - hostCount: event.host_count, // hostCount, - reportedHosts: event.reported_hosts, - successfulCount: event.successful_count, - failedCount: event.failed_count, - changedCount: event.changed_count, - skippedCount: event.skipped_count, - taskActiveClass: '' - }); - SetTaskStyles({ - task: scope.tasks[scope.tasks.length - 1] - }); - }); - $('#tasksMoreRows').fadeOut(400); - scope.tasksLoading = false; - }) - .error(function(data, status) { - $('#tasksMoreRows').fadeOut(400); - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + scope.next_tasks + '. GET returned: ' + status }); - }); - } - }; - - scope.hostResultsScrollDown = function() { - // check for more hosts when user scrolls to bottom of host results list... - if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_host_results) { - $('#hostResultsMoreRows').fadeIn(); - scope.hostResultsLoading = true; - JobDetailService.getNextPage(scope.next_host_results) - .success(function(data) { - scope.next_host_results = data.next; - data.results.forEach(function(row) { - var status, status_text, item, msg; - if (row.event === "runner_on_skipped") { - status = 'skipped'; - } - else if (row.event === "runner_on_unreachable") { - status = 'unreachable'; - } - else { - status = (row.failed) ? 'failed' : (row.changed) ? 'changed' : 'successful'; - } - switch(status) { - case "successful": - status_text = 'OK'; - break; - case "changed": - status_text = "Changed"; - break; - case "failed": - status_text = "Failed"; - break; - case "unreachable": - status_text = "Unreachable"; - break; - case "skipped": - status_text = "Skipped"; - } - if (row.event_data && row.event_data.res) { - item = row.event_data.res.item; - if (typeof item === "object") { - item = JSON.stringify(item); - } - } - msg = ''; - if (row.event_data && row.event_data.res) { - if (typeof row.event_data.res === 'object') { - msg = row.event_data.res.msg; - } else { - msg = row.event_data.res; - } - } - scope.hostResults.push({ - id: row.id, - status: status, - status_text: status_text, - host_id: row.host, - task_id: row.parent, - name: row.event_data.host, - created: row.created, - msg: (row.event_data && row.event_data.res) ? row.event_data.res.msg : '', - item: item - }); - scope.hostResultsLoading = false; - }); - $('#hostResultsMoreRows').fadeOut(400); - }) - .error(function(data, status) { - $('#hostResultsMoreRows').fadeOut(400); - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + scope.next_host_results + '. GET returned: ' + status }); - }); - } - }; - - scope.refresh = function(){ - $scope.$emit('LoadJob'); - }; - - // Click binding for the expand/collapse button on the standard out log - $scope.toggleStdoutFullscreen = function() { - $scope.stdoutFullScreen = !$scope.stdoutFullScreen; - }; - - scope.editSchedule = function() { - // We need to get the schedule's ID out of the related links - // An example of the related schedule link looks like /api/v1/schedules/5 - // where 5 is the ID we are trying to capture - var regex = /\/api\/v1\/schedules\/(\d+)\//; - var id = scope.job.related.schedule.match(regex)[1]; - - if(scope.job.job_template && id) { - $state.go('jobTemplateSchedules.edit', {id: scope.job.job_template, schedule_id: id}); - } - }; - - // SchedulesRefresh is the callback string that we passed to the edit schedule modal - // When the modal successfully updates the schedule it will emit this event and pass - // the updated schedule object - if (scope.removeSchedulesRefresh) { - scope.removeSchedulesRefresh(); - } - scope.$on('SchedulesRefresh', function(e, data) { - if (data) { - scope.scheduled_by = data.name; - } - }); - } -]; From f174afb6df14b6b442e367d6ba281249403e2771 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 1 Mar 2017 16:43:28 -0500 Subject: [PATCH 22/39] Import variables, parse, loadconfig in the correct place --- awx/ui/client/src/app.js | 6 ------ awx/ui/client/src/shared/main.js | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 7123d16dc6..76ffef9df3 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -70,9 +70,6 @@ import jobs from './jobs/main'; import teams from './teams/main'; import users from './users/main'; import projects from './projects/main'; -import variables from './shared/variables/main'; -import parse from './shared/parse/main'; -import loadconfig from './shared/load-config/main'; import RestServices from './rest/main'; import access from './access/main'; @@ -138,9 +135,6 @@ var tower = angular.module('Tower', [ teams.name, users.name, projects.name, - variables.name, - parse.name, - loadconfig.name, //'templates', 'Utilities', 'OrganizationFormDefinition', diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index 5b68027dde..864c95a9ea 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -20,6 +20,9 @@ import templateUrl from './template-url/main'; import RestServices from '../rest/main'; import stateDefinitions from './stateDefinitions.factory'; import apiLoader from './api-loader'; +import variables from './variables/main'; +import parse from './parse/main'; +import loadconfig from './load-config/main'; import 'angular-duration-format'; export default @@ -36,6 +39,9 @@ angular.module('shared', [listGenerator.name, templateUrl.name, RestServices.name, apiLoader.name, + variables.name, + parse.name, + loadconfig.name, require('angular-cookies'), 'angular-duration-format' ]) From 956beba29001a1535f091e6de610c8f2b78d3915 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Thu, 2 Mar 2017 10:51:15 -0500 Subject: [PATCH 23/39] Import shared modules in shared/main.js rather than app.js --- awx/ui/client/src/app.js | 9 --------- awx/ui/client/src/shared/main.js | 12 ++++++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index dd0ce79cbe..c04a867661 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -58,7 +58,6 @@ import breadCrumb from './bread-crumb/main'; import browserData from './browser-data/main'; import configuration from './configuration/main'; import home from './home/main'; -import moment from './shared/moment/main'; import login from './login/main'; import activityStream from './activity-stream/main'; import standardOut from './standard-out/main'; @@ -68,14 +67,8 @@ import jobs from './jobs/main'; 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'; -import './shared/prompt-dialog'; -import './shared/directives'; -import './shared/filters'; -import './shared/features/main'; -import config from './shared/config/main'; import './login/authenticationServices/pendo/ng-pendo'; import footer from './footer/main'; import scheduler from './scheduler/main'; @@ -114,7 +107,6 @@ var tower = angular.module('Tower', [ mainMenu.name, breadCrumb.name, home.name, - moment.name, login.name, activityStream.name, footer.name, @@ -125,7 +117,6 @@ var tower = angular.module('Tower', [ standardOut.name, Templates.name, portalMode.name, - config.name, credentials.name, jobs.name, teams.name, diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index c1c1b606f9..2410efb05c 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -24,6 +24,12 @@ import variables from './variables/main'; import parse from './parse/main'; import loadconfig from './load-config/main'; import Modal from './Modal'; +import moment from './moment/main'; +import config from './config/main'; +import PromptDialog from './prompt-dialog'; +import directives from './directives'; +import filters from './filters'; +import features from './features/main'; import 'angular-duration-format'; export default @@ -44,6 +50,12 @@ angular.module('shared', [listGenerator.name, parse.name, loadconfig.name, Modal.name, + moment.name, + config.name, + PromptDialog.name, + directives.name, + filters.name, + features.name, require('angular-cookies'), 'angular-duration-format' ]) From a2a0a3194f6831569669b3bdbc6ae69831584433 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Thu, 2 Mar 2017 13:10:19 -0800 Subject: [PATCH 24/39] updating $cookieStore to $cookie b/c it was deprecated in Angular 1.4 --- awx/ui/client/src/app.js | 8 +-- .../authentication.service.js | 56 +++++++++---------- .../checkAccess.factory.js | 6 +- .../authenticationServices/timer.factory.js | 8 +-- .../login/loginModal/loginModal.controller.js | 8 +-- .../client/src/rest/restServices.factory.js | 4 +- awx/ui/client/src/shared/Utilities.js | 4 +- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index c04a867661..daff141589 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -232,12 +232,12 @@ var tower = angular.module('Tower', [ // }) } ]) - .run(['$stateExtender', '$q', '$compile', '$cookieStore', '$rootScope', '$log', '$stateParams', + .run(['$stateExtender', '$q', '$compile', '$cookies', '$rootScope', '$log', '$stateParams', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest', 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', 'FeaturesService', '$filter', 'SocketService', - function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log, $stateParams, + function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams, CheckLicense, $location, Authorization, LoadBasePaths, Timer, ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, @@ -339,7 +339,7 @@ var tower = angular.module('Tower', [ // capture most recent URL, excluding login/logout $rootScope.lastPath = $location.path(); $rootScope.enteredPath = $location.path(); - $cookieStore.put('lastPath', $location.path()); + $cookies.put('lastPath', $location.path()); } if (Authorization.isUserLoggedIn() === false) { @@ -405,7 +405,7 @@ var tower = angular.module('Tower', [ // User not authenticated, redirect to login page $location.path('/login'); } else { - var lastUser = $cookieStore.get('current_user'), + var lastUser = $cookies.getObject('current_user'), timestammp = Store('sessionTime'); if (lastUser && lastUser.id && timestammp && timestammp[lastUser.id] && timestammp[lastUser.id].loggedIn) { var stime = timestammp[lastUser.id].time, diff --git a/awx/ui/client/src/login/authenticationServices/authentication.service.js b/awx/ui/client/src/login/authenticationServices/authentication.service.js index 9771bc46c5..1b94e6bd93 100644 --- a/awx/ui/client/src/login/authenticationServices/authentication.service.js +++ b/awx/ui/client/src/login/authenticationServices/authentication.service.js @@ -15,20 +15,20 @@ */ export default - ['$http', '$rootScope', '$location', '$cookieStore', 'GetBasePath', 'Store', '$q', + ['$http', '$rootScope', '$location', '$cookies', 'GetBasePath', 'Store', '$q', '$injector', - function ($http, $rootScope, $location, $cookieStore, GetBasePath, Store, $q, + function ($http, $rootScope, $location, $cookies, GetBasePath, Store, $q, $injector) { return { setToken: function (token, expires) { // set the session cookie - $cookieStore.remove('token'); - $cookieStore.remove('token_expires'); - $cookieStore.remove('userLoggedIn'); - $cookieStore.put('token', token); - $cookieStore.put('token_expires', expires); - $cookieStore.put('userLoggedIn', true); - $cookieStore.put('sessionExpired', false); + $cookies.remove('token'); + $cookies.remove('token_expires'); + $cookies.remove('userLoggedIn'); + $cookies.put('token', token); + $cookies.put('token_expires', expires); + $cookies.put('userLoggedIn', true); + $cookies.put('sessionExpired', false); $rootScope.token = token; $rootScope.userLoggedIn = true; $rootScope.token_expires = expires; @@ -38,14 +38,14 @@ export default isUserLoggedIn: function () { if ($rootScope.userLoggedIn === undefined) { // Browser refresh may have occurred - $rootScope.userLoggedIn = $cookieStore.get('userLoggedIn'); - $rootScope.sessionExpired = $cookieStore.get('sessionExpired'); + $rootScope.userLoggedIn = $cookies.get('userLoggedIn'); + $rootScope.sessionExpired = $cookies.get('sessionExpired'); } return $rootScope.userLoggedIn; }, getToken: function () { - return ($rootScope.token) ? $rootScope.token : $cookieStore.get('token'); + return ($rootScope.token) ? $rootScope.token : $cookies.get('token'); }, retrieveToken: function (username, password) { @@ -83,17 +83,17 @@ export default scope.$destroy(); } - if($cookieStore.get('lastPath')==='/portal'){ - $cookieStore.put( 'lastPath', '/portal'); + if($cookies.get('lastPath')==='/portal'){ + $cookies.put( 'lastPath', '/portal'); $rootScope.lastPath = '/portal'; } - else if ($cookieStore.get('lastPath') !== '/home' || $cookieStore.get('lastPath') !== '/' || $cookieStore.get('lastPath') !== '/login' || $cookieStore.get('lastPath') !== '/logout'){ + else if ($cookies.get('lastPath') !== '/home' || $cookies.get('lastPath') !== '/' || $cookies.get('lastPath') !== '/login' || $cookies.get('lastPath') !== '/logout'){ // do nothing - $rootScope.lastPath = $cookieStore.get('lastPath'); + $rootScope.lastPath = $cookies.get('lastPath'); } else { // your last path was home - $cookieStore.remove('lastPath'); + $cookies.remove('lastPath'); $rootScope.lastPath = '/home'; } x = Store('sessionTime'); @@ -102,17 +102,17 @@ export default } Store('sessionTime', x); - if ($cookieStore.get('current_user')) { - $rootScope.lastUser = $cookieStore.get('current_user').id; + if ($cookies.getObject('current_user')) { + $rootScope.lastUser = $cookies.getObject('current_user').id; } ConfigService.delete(); SocketService.disconnect(); - $cookieStore.remove('token_expires'); - $cookieStore.remove('current_user'); - $cookieStore.remove('token'); - $cookieStore.put('userLoggedIn', false); - $cookieStore.put('sessionExpired', false); - $cookieStore.put('current_user', {}); + $cookies.remove('token_expires'); + $cookies.remove('current_user'); + $cookies.remove('token'); + $cookies.put('userLoggedIn', false); + $cookies.put('sessionExpired', false); + $cookies.putObject('current_user', {}); $rootScope.current_user = {}; $rootScope.license_tested = undefined; $rootScope.userLoggedIn = false; @@ -163,11 +163,11 @@ export default setUserInfo: function (response) { // store the response values in $rootScope so we can get to them later $rootScope.current_user = response.results[0]; - $cookieStore.put('current_user', response.results[0]); //keep in session cookie in the event of browser refresh + $cookies.putObject('current_user', response.results[0]); //keep in session cookie in the event of browser refresh }, restoreUserInfo: function () { - $rootScope.current_user = $cookieStore.get('current_user'); + $rootScope.current_user = $cookies.getObject('current_user'); }, getUserInfo: function (key) { @@ -177,7 +177,7 @@ export default return $rootScope.current_user[key]; } this.restoreUserInfo(); - cu = $cookieStore.get('current_user'); + cu = $cookies.getObject('current_user'); return cu[key]; } }; diff --git a/awx/ui/client/src/login/authenticationServices/checkAccess.factory.js b/awx/ui/client/src/login/authenticationServices/checkAccess.factory.js index 1be4bab4b8..781012b1e9 100644 --- a/awx/ui/client/src/login/authenticationServices/checkAccess.factory.js +++ b/awx/ui/client/src/login/authenticationServices/checkAccess.factory.js @@ -12,8 +12,8 @@ export default - ['$rootScope', 'Alert', 'Rest', 'GetBasePath', 'ProcessErrors', '$cookieStore', - function ($rootScope, Alert, Rest, GetBasePath, ProcessErrors, $cookieStore) { + ['$rootScope', 'Alert', 'Rest', 'GetBasePath', 'ProcessErrors', '$cookies', + function ($rootScope, Alert, Rest, GetBasePath, ProcessErrors, $cookies) { return function (params) { // set PermissionAddAllowed to true or false based on user access. admins and org admins are granted // accesss. @@ -22,7 +22,7 @@ export default me; // uer may have refreshed the browser, in which case retrieve current user info from session cookie - me = ($rootScope.current_user) ? $rootScope.current_user : $cookieStore.get('current_user'); + me = ($rootScope.current_user) ? $rootScope.current_user : $cookies.getObject('current_user'); if (me.is_superuser) { scope.PermissionAddAllowed = true; diff --git a/awx/ui/client/src/login/authenticationServices/timer.factory.js b/awx/ui/client/src/login/authenticationServices/timer.factory.js index ebdf440baf..37d61a9349 100644 --- a/awx/ui/client/src/login/authenticationServices/timer.factory.js +++ b/awx/ui/client/src/login/authenticationServices/timer.factory.js @@ -22,9 +22,9 @@ * @description */ export default - ['$rootScope', '$cookieStore', 'CreateDialog', 'Authorization', + ['$rootScope', '$cookies', 'CreateDialog', 'Authorization', 'Store', '$interval', '$state', '$q', 'i18n', - function ($rootScope, $cookieStore, CreateDialog, Authorization, + function ($rootScope, $cookies, CreateDialog, Authorization, Store, $interval, $state, $q, i18n) { return { @@ -81,7 +81,7 @@ export default } this.sessionTime = 0; this.clearTimers(); - $cookieStore.put('sessionExpired', true); + $cookies.put('sessionExpired', true); }, moveForward: function () { @@ -101,7 +101,7 @@ export default y[$rootScope.current_user.id] = x; Store('sessionTime' , y); $rootScope.sessionExpired = false; - $cookieStore.put('sessionExpired', false); + $cookies.put('sessionExpired', false); this.startTimers(); }, diff --git a/awx/ui/client/src/login/loginModal/loginModal.controller.js b/awx/ui/client/src/login/loginModal/loginModal.controller.js index 24074ac2a1..61ff6b756d 100644 --- a/awx/ui/client/src/login/loginModal/loginModal.controller.js +++ b/awx/ui/client/src/login/loginModal/loginModal.controller.js @@ -54,11 +54,11 @@ * This is usage information. */ -export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', +export default ['$log', '$cookies', '$compile', '$window', '$rootScope', '$location', 'Authorization', 'ToggleClass', 'Alert', 'Wait', 'Timer', 'Empty', 'ClearScope', '$scope', 'pendoService', 'ConfigService', 'CheckLicense', 'FeaturesService', 'SocketService', - function ($log, $cookieStore, $compile, $window, $rootScope, $location, + function ($log, $cookies, $compile, $window, $rootScope, $location, Authorization, ToggleClass, Alert, Wait, Timer, Empty, ClearScope, scope, pendoService, ConfigService, CheckLicense, FeaturesService, SocketService) { @@ -70,13 +70,13 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', }, 1000); }; - scope.sessionExpired = (Empty($rootScope.sessionExpired)) ? $cookieStore.get('sessionExpired') : $rootScope.sessionExpired; + scope.sessionExpired = (Empty($rootScope.sessionExpired)) ? $cookies.get('sessionExpired') : $rootScope.sessionExpired; scope.login_username = ''; scope.login_password = ''; lastPath = function () { - return (Empty($rootScope.lastPath)) ? $cookieStore.get('lastPath') : $rootScope.lastPath; + return (Empty($rootScope.lastPath)) ? $cookies.get('lastPath') : $rootScope.lastPath; }; lastUser = function(){ diff --git a/awx/ui/client/src/rest/restServices.factory.js b/awx/ui/client/src/rest/restServices.factory.js index 7e25c50999..b031237a67 100644 --- a/awx/ui/client/src/rest/restServices.factory.js +++ b/awx/ui/client/src/rest/restServices.factory.js @@ -55,8 +55,8 @@ */ export default - ['$http', '$rootScope', '$cookieStore', '$q', 'Authorization', - function ($http, $rootScope, $cookieStore, $q, Authorization) { + ['$http', '$rootScope', '$cookies', '$q', 'Authorization', + function ($http, $rootScope, $cookies, $q, Authorization) { return { headers: {}, diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index b4e5b31dc8..76f2d320dc 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -179,8 +179,8 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) * @methodOf shared.function:Utilities * @description For handling errors that are returned from the API */ -.factory('ProcessErrors', ['$rootScope', '$cookieStore', '$log', '$location', 'Alert', 'Wait', - function($rootScope, $cookieStore, $log, $location, Alert, Wait) { +.factory('ProcessErrors', ['$rootScope', '$cookies', '$log', '$location', 'Alert', 'Wait', + function($rootScope, $cookies, $log, $location, Alert, Wait) { return function(scope, data, status, form, defaultMsg) { var field, fieldErrors, msg, keys; Wait('stop'); From 4dfe85af7fc1fe41f7480e07b224a19cc00a5e1b Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Thu, 2 Mar 2017 16:59:55 -0500 Subject: [PATCH 25/39] Filters organization --- awx/ui/client/src/app.js | 3 - awx/ui/client/src/filters.js | 14 --- .../list/inventory-list.controller.js | 4 +- awx/ui/client/src/lists/AllJobs.js | 2 +- awx/ui/client/src/lists/CompletedJobs.js | 2 +- awx/ui/client/src/lists/ScheduledJobs.js | 2 +- .../organizations-inventories.controller.js | 4 +- awx/ui/client/src/shared/Utilities.js | 2 +- awx/ui/client/src/shared/capitalize.filter.js | 13 -- awx/ui/client/src/shared/filters.js | 112 ------------------ .../append.filter.js | 0 .../src/shared/filters/capitalize.filter.js | 20 ++++ .../format-epoch.filter.js | 0 .../src/shared/filters/is-empty.filter.js | 17 +++ .../src/shared/filters/long-date.filter.js | 37 ++++++ awx/ui/client/src/shared/filters/main.js | 17 +++ .../prepend.filter.js | 0 .../shared/filters/xss-sanitizer.filter.js | 20 ++++ awx/ui/client/src/shared/format-epoch/main.js | 9 -- awx/ui/client/src/shared/long-date.filter.js | 25 ---- awx/ui/client/src/shared/main.js | 5 +- awx/ui/client/src/shared/prompt-dialog.js | 2 +- .../client/src/shared/string-filters/main.js | 7 -- .../client/src/shared/xss-sanitizer.filter.js | 13 -- .../compare-facts/fact-template.js | 5 +- 25 files changed, 123 insertions(+), 212 deletions(-) delete mode 100644 awx/ui/client/src/filters.js delete mode 100644 awx/ui/client/src/shared/capitalize.filter.js delete mode 100644 awx/ui/client/src/shared/filters.js rename awx/ui/client/src/shared/{string-filters => filters}/append.filter.js (100%) create mode 100644 awx/ui/client/src/shared/filters/capitalize.filter.js rename awx/ui/client/src/shared/{format-epoch => filters}/format-epoch.filter.js (100%) create mode 100644 awx/ui/client/src/shared/filters/is-empty.filter.js create mode 100644 awx/ui/client/src/shared/filters/long-date.filter.js create mode 100644 awx/ui/client/src/shared/filters/main.js rename awx/ui/client/src/shared/{string-filters => filters}/prepend.filter.js (100%) create mode 100644 awx/ui/client/src/shared/filters/xss-sanitizer.filter.js delete mode 100644 awx/ui/client/src/shared/format-epoch/main.js delete mode 100644 awx/ui/client/src/shared/long-date.filter.js delete mode 100644 awx/ui/client/src/shared/string-filters/main.js delete mode 100644 awx/ui/client/src/shared/xss-sanitizer.filter.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index c04a867661..03934afedd 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -39,7 +39,6 @@ if ($basePath) { // Modules import './forms'; import './lists'; -import './filters'; import portalMode from './portal-mode/main'; import systemTracking from './system-tracking/main'; import inventories from './inventories/main'; @@ -135,7 +134,6 @@ var tower = angular.module('Tower', [ 'InventoryFormDefinition', 'InventoryGroupsDefinition', 'InventoryHostsDefinition', - 'AWFilters', 'HostFormDefinition', 'HostListDefinition', 'GroupFormDefinition', @@ -166,7 +164,6 @@ var tower = angular.module('Tower', [ 'lrInfiniteScroll', 'PortalJobsListDefinition', 'features', - 'longDateFilter', 'pendolytics', scheduler.name, 'WorkflowFormDefinition', diff --git a/awx/ui/client/src/filters.js b/awx/ui/client/src/filters.js deleted file mode 100644 index e6549bb779..0000000000 --- a/awx/ui/client/src/filters.js +++ /dev/null @@ -1,14 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import sanitizeFilter from './shared/xss-sanitizer.filter'; -import capitalizeFilter from './shared/capitalize.filter'; -import longDateFilter from './shared/long-date.filter'; -export { - sanitizeFilter, - capitalizeFilter, - longDateFilter -}; diff --git a/awx/ui/client/src/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories/list/inventory-list.controller.js index 6b8a75f0cc..59f36574ff 100644 --- a/awx/ui/client/src/inventories/list/inventory-list.controller.js +++ b/awx/ui/client/src/inventories/list/inventory-list.controller.js @@ -11,7 +11,7 @@ */ function InventoriesList($scope, $rootScope, $location, $log, - $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, Prompt, + $stateParams, $compile, $filter, Rest, Alert, InventoryList, Prompt, ClearScope, ProcessErrors, GetBasePath, Wait, Find, Empty, $state, rbacUiControlService, Dataset) { let list = InventoryList, @@ -304,6 +304,6 @@ function InventoriesList($scope, $rootScope, $location, $log, } export default ['$scope', '$rootScope', '$location', '$log', - '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', + '$stateParams', '$compile', '$filter', 'Rest', 'Alert', 'InventoryList', 'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'rbacUiControlService', 'Dataset', InventoriesList ]; diff --git a/awx/ui/client/src/lists/AllJobs.js b/awx/ui/client/src/lists/AllJobs.js index b3ac5c5b71..77b8955b7b 100644 --- a/awx/ui/client/src/lists/AllJobs.js +++ b/awx/ui/client/src/lists/AllJobs.js @@ -6,7 +6,7 @@ export default - angular.module('AllJobsDefinition', ['sanitizeFilter', 'capitalizeFilter']) + angular.module('AllJobsDefinition', []) .factory('AllJobsList', ['i18n', function(i18n) { return { diff --git a/awx/ui/client/src/lists/CompletedJobs.js b/awx/ui/client/src/lists/CompletedJobs.js index a83281cbd3..d78b1042b7 100644 --- a/awx/ui/client/src/lists/CompletedJobs.js +++ b/awx/ui/client/src/lists/CompletedJobs.js @@ -6,7 +6,7 @@ export default - angular.module('CompletedJobsDefinition', ['sanitizeFilter']) + angular.module('CompletedJobsDefinition', []) .factory('CompletedJobsList', ['i18n', function(i18n) { return { // These tooltip fields are consumed to build disabled related tabs tooltips in the form > add view diff --git a/awx/ui/client/src/lists/ScheduledJobs.js b/awx/ui/client/src/lists/ScheduledJobs.js index 8c016cfb9f..3688cf7058 100644 --- a/awx/ui/client/src/lists/ScheduledJobs.js +++ b/awx/ui/client/src/lists/ScheduledJobs.js @@ -6,7 +6,7 @@ export default - angular.module('ScheduledJobsDefinition', ['sanitizeFilter']) + angular.module('ScheduledJobsDefinition', []) .factory('ScheduledJobsList', ['i18n', function(i18n) { return { diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js index ae92d23058..6d171f98c2 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js @@ -5,11 +5,11 @@ *************************************************/ export default ['$scope', '$rootScope', '$location', '$log', - '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', + '$stateParams', '$compile', '$filter', 'Rest', 'Alert', 'InventoryList', 'generateList', 'Prompt', 'ReturnToCaller', 'OrgInventoryDataset', 'OrgInventoryList', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', function($scope, $rootScope, $location, $log, - $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, + $stateParams, $compile, $filter, Rest, Alert, InventoryList, generateList, Prompt, ReturnToCaller, Dataset, OrgInventoryList, ClearScope, ProcessErrors, GetBasePath, Wait, Find, Empty, $state) { diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index b4e5b31dc8..e344f9cc33 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -18,7 +18,7 @@ export default -angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) +angular.module('Utilities', ['RestServices', 'Utilities']) /** * @ngdoc method diff --git a/awx/ui/client/src/shared/capitalize.filter.js b/awx/ui/client/src/shared/capitalize.filter.js deleted file mode 100644 index 598be4218c..0000000000 --- a/awx/ui/client/src/shared/capitalize.filter.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - angular.module('capitalizeFilter', []).filter('capitalize', function() { - return function(input) { - input = input.charAt(0).toUpperCase() + input.substr(1).toLowerCase(); - return input; - }; - }); diff --git a/awx/ui/client/src/shared/filters.js b/awx/ui/client/src/shared/filters.js deleted file mode 100644 index 50014c06ce..0000000000 --- a/awx/ui/client/src/shared/filters.js +++ /dev/null @@ -1,112 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name shared.function:filters - * @description - * Custom filters - * - */ - - - -export default -angular.module('AWFilters', []) - - // Object is empty / undefined / null - .filter('isEmpty', function () { - var key; - return function (obj) { - for (key in obj) { - if (obj.hasOwnProperty(key)) { - return false; - } - } - return true; - }; - }) - - // - // capitalize -capitalize the first letter of each word - // - .filter('capitalize', [ function () { - return function (input) { - var values, result, i; - if (input) { - values = input.replace(/\_/g, ' ').split(" "); - result = ""; - for (i = 0; i < values.length; i++) { - result += values[i].charAt(0).toUpperCase() + values[i].substr(1) + ' '; - } - return result.trim(); - } - }; - }]) - - // - // Filter an object of objects by id using an array of id values - // Created specifically for Filter Events on job detail page. - // - .filter('FilterById', [ function() { - return function(input, list) { - var results = []; - if (input && list.length > 0) { - list.forEach(function(itm) { - input.forEach(function(row) { - if (row.id === itm) { - results.push(row); - } - }); - }); - return results; - } - return input; - }; - }]) - - .filter('FilterByField', [ function() { - return function(input, list) { - var fld, key, search = {}, results = {}; - for (fld in list) { - if (list[fld]) { - search[fld] = list[fld]; - } - } - if (Object.keys(search).length > 0) { - for (fld in search) { - for (key in input) { - if (input[key][fld] === search[fld]) { - results[key] = input[key]; - } - } - } - return results; - } - return input; - }; - }]) - - .filter('FilterFailedEvents', [ function() { - return function(input, liveEventProcessing, searchAllStatus) { - var results = []; - if (liveEventProcessing) { - // while live events are happening, we don't want angular to filter out anything - return input; - } - else if (searchAllStatus === 'failed') { - // filter by failed - input.forEach(function(row) { - if (row.status === 'failed') { - results.push(row); - } - }); - return results; - } - return input; - }; - }]); diff --git a/awx/ui/client/src/shared/string-filters/append.filter.js b/awx/ui/client/src/shared/filters/append.filter.js similarity index 100% rename from awx/ui/client/src/shared/string-filters/append.filter.js rename to awx/ui/client/src/shared/filters/append.filter.js diff --git a/awx/ui/client/src/shared/filters/capitalize.filter.js b/awx/ui/client/src/shared/filters/capitalize.filter.js new file mode 100644 index 0000000000..7b5db1de0e --- /dev/null +++ b/awx/ui/client/src/shared/filters/capitalize.filter.js @@ -0,0 +1,20 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + export default [function() { + return function(input) { + input = input.charAt(0).toUpperCase() + input.substr(1).toLowerCase(); + return input; + }; + }]; + +// export default +// angular.module('capitalizeFilter', []).filter('capitalize', function() { +// return function(input) { +// input = input.charAt(0).toUpperCase() + input.substr(1).toLowerCase(); +// return input; +// }; +// }); diff --git a/awx/ui/client/src/shared/format-epoch/format-epoch.filter.js b/awx/ui/client/src/shared/filters/format-epoch.filter.js similarity index 100% rename from awx/ui/client/src/shared/format-epoch/format-epoch.filter.js rename to awx/ui/client/src/shared/filters/format-epoch.filter.js diff --git a/awx/ui/client/src/shared/filters/is-empty.filter.js b/awx/ui/client/src/shared/filters/is-empty.filter.js new file mode 100644 index 0000000000..a0ece767f1 --- /dev/null +++ b/awx/ui/client/src/shared/filters/is-empty.filter.js @@ -0,0 +1,17 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + export default function() { + return function (obj) { + var key; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + return false; + } + } + return true; + }; + } diff --git a/awx/ui/client/src/shared/filters/long-date.filter.js b/awx/ui/client/src/shared/filters/long-date.filter.js new file mode 100644 index 0000000000..03f1dd8725 --- /dev/null +++ b/awx/ui/client/src/shared/filters/long-date.filter.js @@ -0,0 +1,37 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + export default ['moment', function(moment) { + return function(input) { + var date; + if(input === null){ + return ""; + }else { + date = moment(input); + return date.format('l LTS'); + } + }; + }]; + + +// function longDateFilter(moment, input) { +// var date; +// if(input === null){ +// return ""; +// }else { +// date = moment(input); +// return date.format('l LTS'); +// } +// } +// +// export default +// angular.module('longDateFilter', []) +// .filter('longDate', +// [ 'moment', +// function(moment) { +// return _.partial(longDateFilter, moment); +// } +// ]); diff --git a/awx/ui/client/src/shared/filters/main.js b/awx/ui/client/src/shared/filters/main.js new file mode 100644 index 0000000000..2298bb0723 --- /dev/null +++ b/awx/ui/client/src/shared/filters/main.js @@ -0,0 +1,17 @@ +import prepend from './prepend.filter'; +import append from './append.filter'; +import isEmpty from './is-empty.filter'; +import capitalize from './capitalize.filter'; +import longDate from './long-date.filter'; +import sanitize from './xss-sanitizer.filter'; +import formatEpoch from './format-epoch.filter'; + +export default + angular.module('stringFilters', []) + .filter('prepend', prepend) + .filter('append', append) + .filter('isEmpty', isEmpty) + .filter('capitalize', capitalize) + .filter('longDate', longDate) + .filter('sanitize', sanitize) + .filter('formatEpoch', formatEpoch); diff --git a/awx/ui/client/src/shared/string-filters/prepend.filter.js b/awx/ui/client/src/shared/filters/prepend.filter.js similarity index 100% rename from awx/ui/client/src/shared/string-filters/prepend.filter.js rename to awx/ui/client/src/shared/filters/prepend.filter.js diff --git a/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js b/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js new file mode 100644 index 0000000000..8382505725 --- /dev/null +++ b/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js @@ -0,0 +1,20 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + export default [function() { + return function(input) { + input = $("").text(input)[0].innerHTML; + return input; + }; + }]; + +// export default +// angular.module('sanitizeFilter', []).filter('sanitize', function() { +// return function(input) { +// input = $("").text(input)[0].innerHTML; +// return input; +// }; +// }); diff --git a/awx/ui/client/src/shared/format-epoch/main.js b/awx/ui/client/src/shared/format-epoch/main.js deleted file mode 100644 index df93378aff..0000000000 --- a/awx/ui/client/src/shared/format-epoch/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import formatEpoch from './format-epoch.filter'; -import moment from '../moment/main'; - -export default - angular.module('formatEpoch', - [ moment.name - ]) - .filter('formatEpoch', formatEpoch); - diff --git a/awx/ui/client/src/shared/long-date.filter.js b/awx/ui/client/src/shared/long-date.filter.js deleted file mode 100644 index f48b014f56..0000000000 --- a/awx/ui/client/src/shared/long-date.filter.js +++ /dev/null @@ -1,25 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -function longDateFilter(moment, input) { - var date; - if(input === null){ - return ""; - }else { - date = moment(input); - return date.format('l LTS'); - } -} - -export default - angular.module('longDateFilter', []) - .filter('longDate', - [ 'moment', - function(moment) { - return _.partial(longDateFilter, moment); - } - ]); - diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index 2410efb05c..82daaf238d 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -11,7 +11,7 @@ import smartSearch from './smart-search/main'; import paginate from './paginate/main'; import columnSort from './column-sort/main'; import lodashAsPromised from './lodash-as-promised'; -import stringFilters from './string-filters/main'; +import filters from './filters/main'; import truncatedText from './truncated-text.directive'; import stateExtender from './stateExtender.provider'; import rbacUiControl from './rbacUiControl'; @@ -28,7 +28,6 @@ import moment from './moment/main'; import config from './config/main'; import PromptDialog from './prompt-dialog'; import directives from './directives'; -import filters from './filters'; import features from './features/main'; import 'angular-duration-format'; @@ -39,7 +38,7 @@ angular.module('shared', [listGenerator.name, smartSearch.name, paginate.name, columnSort.name, - stringFilters.name, + filters.name, 'ui.router', rbacUiControl.name, socket.name, diff --git a/awx/ui/client/src/shared/prompt-dialog.js b/awx/ui/client/src/shared/prompt-dialog.js index f0c7794e47..96152c0b89 100644 --- a/awx/ui/client/src/shared/prompt-dialog.js +++ b/awx/ui/client/src/shared/prompt-dialog.js @@ -30,7 +30,7 @@ */ export default -angular.module('PromptDialog', ['Utilities', 'sanitizeFilter']) +angular.module('PromptDialog', ['Utilities']) .factory('Prompt', [ function () { return function (params) { diff --git a/awx/ui/client/src/shared/string-filters/main.js b/awx/ui/client/src/shared/string-filters/main.js deleted file mode 100644 index 3c51d184d2..0000000000 --- a/awx/ui/client/src/shared/string-filters/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import prepend from './prepend.filter'; -import append from './append.filter'; - -export default - angular.module('stringFilters', []) - .filter('prepend', prepend) - .filter('append', append); diff --git a/awx/ui/client/src/shared/xss-sanitizer.filter.js b/awx/ui/client/src/shared/xss-sanitizer.filter.js deleted file mode 100644 index daa9a71d68..0000000000 --- a/awx/ui/client/src/shared/xss-sanitizer.filter.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - angular.module('sanitizeFilter', []).filter('sanitize', function() { - return function(input) { - input = $("").text(input)[0].innerHTML; - return input; - }; - }); diff --git a/awx/ui/client/src/system-tracking/compare-facts/fact-template.js b/awx/ui/client/src/system-tracking/compare-facts/fact-template.js index 3dcc29a92a..506e893f2c 100644 --- a/awx/ui/client/src/system-tracking/compare-facts/fact-template.js +++ b/awx/ui/client/src/system-tracking/compare-facts/fact-template.js @@ -1,7 +1,4 @@ -import stringFilters from '../../shared/string-filters/main'; -import formatEpoch from '../../shared/format-epoch/main'; - -var $injector = angular.injector(['ng', stringFilters.name, formatEpoch.name]); +var $injector = angular.injector(['ng']); var $interpolate = $injector.get('$interpolate'); function FactTemplate(templateString) { From 506fae0b9ceb7a3eb5485e39ab3e3dd6ef6bf8ab Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 3 Mar 2017 11:50:20 -0500 Subject: [PATCH 26/39] First pass at unit tests for the filters in shared/filters --- .../src/shared/filters/append.filter.js | 13 +++++++--- .../src/shared/filters/capitalize.filter.js | 8 ------ .../src/shared/filters/format-epoch.filter.js | 2 -- .../src/shared/filters/long-date.filter.js | 22 +--------------- .../src/shared/filters/prepend.filter.js | 12 ++++++--- .../shared/filters/xss-sanitizer.filter.js | 8 ------ .../spec/shared/filters/append.filter-test.js | 25 +++++++++++++++++++ .../shared/filters/capitalize.filter-test.js | 20 +++++++++++++++ .../filters/format-epoch.filter-test.js | 18 +++++++++++++ .../shared/filters/is-empty.filter-test.js | 18 +++++++++++++ .../shared/filters/long-date.filter-test.js | 20 +++++++++++++++ .../shared/filters/prepend.filter-test.js | 25 +++++++++++++++++++ .../filters/xss-sanitizer.filter-test.js | 17 +++++++++++++ 13 files changed, 162 insertions(+), 46 deletions(-) create mode 100644 awx/ui/tests/spec/shared/filters/append.filter-test.js create mode 100644 awx/ui/tests/spec/shared/filters/capitalize.filter-test.js create mode 100644 awx/ui/tests/spec/shared/filters/format-epoch.filter-test.js create mode 100644 awx/ui/tests/spec/shared/filters/is-empty.filter-test.js create mode 100644 awx/ui/tests/spec/shared/filters/long-date.filter-test.js create mode 100644 awx/ui/tests/spec/shared/filters/prepend.filter-test.js create mode 100644 awx/ui/tests/spec/shared/filters/xss-sanitizer.filter-test.js diff --git a/awx/ui/client/src/shared/filters/append.filter.js b/awx/ui/client/src/shared/filters/append.filter.js index 42860cd499..1493cf5997 100644 --- a/awx/ui/client/src/shared/filters/append.filter.js +++ b/awx/ui/client/src/shared/filters/append.filter.js @@ -1,10 +1,15 @@ export default function() { return function(string, append) { if (string) { - return string + append; + if (append) { + return string + append; + } + else { + return string; + } + } + else { + return ""; } - - return ""; }; } - diff --git a/awx/ui/client/src/shared/filters/capitalize.filter.js b/awx/ui/client/src/shared/filters/capitalize.filter.js index 7b5db1de0e..859dd367e1 100644 --- a/awx/ui/client/src/shared/filters/capitalize.filter.js +++ b/awx/ui/client/src/shared/filters/capitalize.filter.js @@ -10,11 +10,3 @@ return input; }; }]; - -// export default -// angular.module('capitalizeFilter', []).filter('capitalize', function() { -// return function(input) { -// input = input.charAt(0).toUpperCase() + input.substr(1).toLowerCase(); -// return input; -// }; -// }); diff --git a/awx/ui/client/src/shared/filters/format-epoch.filter.js b/awx/ui/client/src/shared/filters/format-epoch.filter.js index 2216249732..9d8fee351f 100644 --- a/awx/ui/client/src/shared/filters/format-epoch.filter.js +++ b/awx/ui/client/src/shared/filters/format-epoch.filter.js @@ -12,5 +12,3 @@ export default }; } ]; - - diff --git a/awx/ui/client/src/shared/filters/long-date.filter.js b/awx/ui/client/src/shared/filters/long-date.filter.js index 03f1dd8725..f2d058e3b2 100644 --- a/awx/ui/client/src/shared/filters/long-date.filter.js +++ b/awx/ui/client/src/shared/filters/long-date.filter.js @@ -7,7 +7,7 @@ export default ['moment', function(moment) { return function(input) { var date; - if(input === null){ + if(input === null || input === undefined){ return ""; }else { date = moment(input); @@ -15,23 +15,3 @@ } }; }]; - - -// function longDateFilter(moment, input) { -// var date; -// if(input === null){ -// return ""; -// }else { -// date = moment(input); -// return date.format('l LTS'); -// } -// } -// -// export default -// angular.module('longDateFilter', []) -// .filter('longDate', -// [ 'moment', -// function(moment) { -// return _.partial(longDateFilter, moment); -// } -// ]); diff --git a/awx/ui/client/src/shared/filters/prepend.filter.js b/awx/ui/client/src/shared/filters/prepend.filter.js index a9d87be94c..93bad2889d 100644 --- a/awx/ui/client/src/shared/filters/prepend.filter.js +++ b/awx/ui/client/src/shared/filters/prepend.filter.js @@ -1,9 +1,15 @@ export default function() { return function(string, prepend) { if (string) { - return prepend + string; + if(prepend) { + return prepend + string; + } + else { + return string; + } + } + else { + return ""; } - - return ""; }; } diff --git a/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js b/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js index 8382505725..ef1c16d32d 100644 --- a/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js +++ b/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js @@ -10,11 +10,3 @@ return input; }; }]; - -// export default -// angular.module('sanitizeFilter', []).filter('sanitize', function() { -// return function(input) { -// input = $("").text(input)[0].innerHTML; -// return input; -// }; -// }); diff --git a/awx/ui/tests/spec/shared/filters/append.filter-test.js b/awx/ui/tests/spec/shared/filters/append.filter-test.js new file mode 100644 index 0000000000..f8a1ddcd47 --- /dev/null +++ b/awx/ui/tests/spec/shared/filters/append.filter-test.js @@ -0,0 +1,25 @@ +'use strict'; + +describe('Filter: append', () => { + var filter; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(function(){ + inject(function($injector){ + filter = $injector.get('$filter')('append'); + }); + }); + + it('should append the two parameters passed', function(){ + expect(filter("foo", "bar")).toBe("foobar"); + }); + + it('should return string if no append param passed', function(){ + expect(filter("foo")).toBe("foo"); + }); + + it('should return empty string if no params passed', function(){ + expect(filter()).toBe(""); + }); +}); diff --git a/awx/ui/tests/spec/shared/filters/capitalize.filter-test.js b/awx/ui/tests/spec/shared/filters/capitalize.filter-test.js new file mode 100644 index 0000000000..d0f939c480 --- /dev/null +++ b/awx/ui/tests/spec/shared/filters/capitalize.filter-test.js @@ -0,0 +1,20 @@ +'use strict'; + +describe('Filter: capitalize', () => { + var filter; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(function(){ + inject(function($injector){ + filter = $injector.get('$filter')('capitalize'); + }); + }); + + it('should capitalize the first letter', function(){ + expect(filter("foo")).toBe("Foo"); + expect(filter("Foo")).toBe("Foo"); + expect(filter("FOO")).toBe("Foo"); + expect(filter("FoO")).toBe("Foo"); + }); +}); diff --git a/awx/ui/tests/spec/shared/filters/format-epoch.filter-test.js b/awx/ui/tests/spec/shared/filters/format-epoch.filter-test.js new file mode 100644 index 0000000000..6bc23aa51b --- /dev/null +++ b/awx/ui/tests/spec/shared/filters/format-epoch.filter-test.js @@ -0,0 +1,18 @@ +'use strict'; + +describe('Filter: formatEpoch', () => { + var filter; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(function(){ + inject(function($injector){ + filter = $injector.get('$filter')('formatEpoch'); + }); + }); + + it('should convert epoch to datetime string', function(){ + expect(filter(11111)).toBe("Dec 31, 1969 10:05 PM"); + expect(filter(610430400)).toBe("May 6, 1989 12:00 AM"); + }); +}); diff --git a/awx/ui/tests/spec/shared/filters/is-empty.filter-test.js b/awx/ui/tests/spec/shared/filters/is-empty.filter-test.js new file mode 100644 index 0000000000..bbb156bf48 --- /dev/null +++ b/awx/ui/tests/spec/shared/filters/is-empty.filter-test.js @@ -0,0 +1,18 @@ +'use strict'; + +describe('Filter: isEmpty', () => { + var filter; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(function(){ + inject(function($injector){ + filter = $injector.get('$filter')('isEmpty'); + }); + }); + + it('check if an object is empty', function(){ + expect(filter({})).toBe(true); + expect(filter({foo: 'bar'})).toBe(false); + }); +}); diff --git a/awx/ui/tests/spec/shared/filters/long-date.filter-test.js b/awx/ui/tests/spec/shared/filters/long-date.filter-test.js new file mode 100644 index 0000000000..579962492c --- /dev/null +++ b/awx/ui/tests/spec/shared/filters/long-date.filter-test.js @@ -0,0 +1,20 @@ +'use strict'; + +describe('Filter: longDate', () => { + var filter; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(function(){ + inject(function($injector){ + filter = $injector.get('$filter')('longDate'); + }); + }); + + it('should convert the timestamp to a UI friendly date and time', function(){ + expect(filter("2017-02-13T22:00:14.106Z")).toBe("2/13/2017 5:00:14 PM"); + }); + it('should return an empty string if no timestamp is passed', function(){ + expect(filter()).toBe(""); + }); +}); diff --git a/awx/ui/tests/spec/shared/filters/prepend.filter-test.js b/awx/ui/tests/spec/shared/filters/prepend.filter-test.js new file mode 100644 index 0000000000..f22c077ecd --- /dev/null +++ b/awx/ui/tests/spec/shared/filters/prepend.filter-test.js @@ -0,0 +1,25 @@ +'use strict'; + +describe('Filter: prepend', () => { + var filter; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(function(){ + inject(function($injector){ + filter = $injector.get('$filter')('prepend'); + }); + }); + + it('should prepend the second param to the first', function(){ + expect(filter("foo", "bar")).toBe("barfoo"); + }); + + it('should return string if no prepend param passed', function(){ + expect(filter("foo")).toBe("foo"); + }); + + it('should return empty string if no params passed', function(){ + expect(filter()).toBe(""); + }); +}); diff --git a/awx/ui/tests/spec/shared/filters/xss-sanitizer.filter-test.js b/awx/ui/tests/spec/shared/filters/xss-sanitizer.filter-test.js new file mode 100644 index 0000000000..88cb3458e1 --- /dev/null +++ b/awx/ui/tests/spec/shared/filters/xss-sanitizer.filter-test.js @@ -0,0 +1,17 @@ +'use strict'; + +describe('Filter: sanitize', () => { + var filter; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(function(){ + inject(function($injector){ + filter = $injector.get('$filter')('sanitize'); + }); + }); + + it('should sanitize xss-vulnerable strings', function(){ + expect(filter("
foobar
")).toBe("<div>foobar</div>"); + }); +}); From 27fc64eb566c8e7497fefee8df01571e21e25381 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Wed, 8 Mar 2017 12:30:30 -0500 Subject: [PATCH 27/39] value_to_python should encode lookup fields as ascii --- awx/api/filters.py | 5 +++++ awx/main/tests/unit/api/test_filters.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/awx/api/filters.py b/awx/api/filters.py index e5c9c39264..47b54da9ab 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -148,6 +148,11 @@ class FieldLookupBackend(BaseFilterBackend): return field.to_python(value) def value_to_python(self, model, lookup, value): + try: + lookup = lookup.encode("ascii") + except UnicodeEncodeError: + raise ValueError("%r is not an allowed field name. Must be ascii encodable." % lookup) + field, new_lookup = self.get_field_from_lookup(model, lookup) # Type names are stored without underscores internally, but are presented and diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py index 6570ada6f7..45eec0df1f 100644 --- a/awx/main/tests/unit/api/test_filters.py +++ b/awx/main/tests/unit/api/test_filters.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import pytest from rest_framework.exceptions import PermissionDenied @@ -24,6 +26,14 @@ def test_valid_in(valid_value): assert 'foo' in value +def test_invalid_field(): + invalid_field = u"ヽヾ" + field_lookup = FieldLookupBackend() + with pytest.raises(ValueError) as excinfo: + field_lookup.value_to_python(WorkflowJobTemplate, invalid_field, 'foo') + assert 'is not an allowed field name. Must be ascii encodable.' in excinfo.value.message + + @pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in']) @pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS) def test_filter_on_password_field(password_field, lookup_suffix): From 0fa4b166605bf0175677a38cedc2a35cc71bc153 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 8 Mar 2017 15:18:21 -0500 Subject: [PATCH 28/39] Rebuild unit-test image for every branch This variable is set by Jenkins, you'll have to set it manually if you build locally. --- tools/docker-compose/unit-tests/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-compose/unit-tests/docker-compose.yml b/tools/docker-compose/unit-tests/docker-compose.yml index 6cb9096ec9..76a4a40342 100644 --- a/tools/docker-compose/unit-tests/docker-compose.yml +++ b/tools/docker-compose/unit-tests/docker-compose.yml @@ -5,7 +5,7 @@ services: build: context: ../../../ dockerfile: tools/docker-compose/unit-tests/Dockerfile - image: gcr.io/ansible-tower-engineering/unit-test-runner:latest + image: gcr.io/ansible-tower-engineering/unit-test-runner:${GIT_BRANCH} environment: SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl" TEST_DIRS: awx/main/tests/functional awx/main/tests/unit awx/conf/tests awx/sso/tests From 34990c304a483bb223e45b3cf47d127c95208f3f Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 8 Mar 2017 15:18:52 -0500 Subject: [PATCH 29/39] Remove npm hacks from unit-test-runner This hasnt been necessary for a while, just forgot it was here.. --- tools/docker-compose/unit-tests/Dockerfile | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tools/docker-compose/unit-tests/Dockerfile b/tools/docker-compose/unit-tests/Dockerfile index 4658c5d32e..d1fec4e583 100644 --- a/tools/docker-compose/unit-tests/Dockerfile +++ b/tools/docker-compose/unit-tests/Dockerfile @@ -7,15 +7,6 @@ RUN yum install -y bzip2 gcc-c++ # We switch the WORKDIR to /ansible-tower further down. WORKDIR "/tmp/" -# Build front-end deps - -# https://github.com/npm/npm/issues/9863 -RUN cd $(npm root -g)/npm \ - && npm install fs-extra \ - && sed -i -e s/graceful-fs/fs-extra/ -e s/fs\.rename/fs.move/ ./lib/utils/rename.js - -RUN npm install -g npm@3.10.7 - COPY awx/ui/package.json awx/ui/ RUN npm set progress=false From 8c1de7f1098fd6003543775e8fd776151d25a58e Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 8 Mar 2017 13:49:19 -0500 Subject: [PATCH 30/39] add an API endpoint for testing external log aggregrator connectivity see: #5164 --- awx/api/permissions.py | 10 ++- awx/conf/urls.py | 1 + awx/conf/views.py | 28 +++++++ .../tests/functional/api/test_settings.py | 60 +++++++++++++ awx/main/tests/unit/utils/test_handlers.py | 84 ++++++++++++++----- awx/main/utils/handlers.py | 30 +++++++ 6 files changed, 190 insertions(+), 23 deletions(-) diff --git a/awx/api/permissions.py b/awx/api/permissions.py index 8ec26a2cc8..966cf95ea5 100644 --- a/awx/api/permissions.py +++ b/awx/api/permissions.py @@ -16,7 +16,8 @@ from awx.main.utils import get_object_or_400 logger = logging.getLogger('awx.api.permissions') __all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission', - 'TaskPermission', 'ProjectUpdatePermission', 'UserPermission',] + 'TaskPermission', 'ProjectUpdatePermission', 'UserPermission', + 'IsSuperUser'] class ModelAccessPermission(permissions.BasePermission): @@ -208,3 +209,10 @@ class UserPermission(ModelAccessPermission): raise PermissionDenied() +class IsSuperUser(permissions.BasePermission): + """ + Allows access only to admin users. + """ + + def has_permission(self, request, view): + return request.user and request.user.is_superuser diff --git a/awx/conf/urls.py b/awx/conf/urls.py index 15505f4c3c..2c4f3ec91d 100644 --- a/awx/conf/urls.py +++ b/awx/conf/urls.py @@ -12,4 +12,5 @@ urlpatterns = patterns( 'awx.conf.views', url(r'^$', 'setting_category_list'), url(r'^(?P[a-z0-9-]+)/$', 'setting_singleton_detail'), + url(r'^logging/test/$', 'setting_logging_test'), ) diff --git a/awx/conf/views.py b/awx/conf/views.py index 99a3daab99..68b399444e 100644 --- a/awx/conf/views.py +++ b/awx/conf/views.py @@ -19,7 +19,9 @@ from rest_framework import status # Tower from awx.api.generics import * # noqa +from awx.api.permissions import IsSuperUser from awx.main.utils import * # noqa +from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException from awx.conf.license import get_licensed_features from awx.conf.models import Setting from awx.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer @@ -130,6 +132,32 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView): settings.TOWER_URL_BASE = url +class SettingLoggingTest(GenericAPIView): + + view_name = _('Logging Connectivity Test') + model = Setting + serializer_class = SettingSingletonSerializer + permission_classes = (IsSuperUser,) + filter_backends = [] + new_in_320 = True + + def post(self, request, *args, **kwargs): + defaults = dict() + for key in settings_registry.get_registered_settings(category_slug='logging'): + try: + defaults[key] = settings_registry.get_setting_field(key).get_default() + except serializers.SkipField: + defaults[key] = None + obj = type('Settings', (object,), defaults)() + serializer = self.get_serializer(obj, data=request.data) + serializer.is_valid(raise_exception=True) + try: + BaseHTTPSHandler.perform_test(serializer.validated_data) + except LoggingConnectivityException as e: + return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return Response(status=status.HTTP_200_OK) + + # Create view functions for all of the class-based views to simplify inclusion # in URL patterns and reverse URL lookups, converting CamelCase names to # lowercase_with_underscore (e.g. MyView.as_view() becomes my_view). diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index 6322f354e7..77b5294203 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -2,14 +2,19 @@ # All Rights Reserved. # Python +from collections import OrderedDict import pytest import os +# Mock +import mock + # Django from django.core.urlresolvers import reverse # AWX from awx.conf.models import Setting +from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException TEST_GIF_LOGO = 'data:image/gif;base64,R0lGODlhIQAjAPIAAP//////AP8AAMzMAJmZADNmAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAHACwAAAAAIQAjAAADo3i63P4wykmrvTjrzZsxXfR94WMQBFh6RECuixHMLyzPQ13ewZCvow9OpzEAjIBj79cJJmU+FceIVEZ3QRozxBttmyOBwPBtisdX4Bha3oxmS+llFIPHQXQKkiSEXz9PeklHBzx3hYNyEHt4fmmAhHp8Nz45KgV5FgWFOFEGmwWbGqEfniChohmoQZ+oqRiZDZhEgk81I4mwg4EKVbxzrDHBEAkAIfkECQoABwAsAAAAACEAIwAAA6V4utz+MMpJq724GpP15p1kEAQYQmOwnWjgrmxjuMEAx8rsDjZ+fJvdLWQAFAHGWo8FRM54JqIRmYTigDrDMqZTbbbMj0CgjTLHZKvPQH6CTx+a2vKR0XbbOsoZ7SphG057gjl+c0dGgzeGNiaBiSgbBQUHBV08NpOVlkMSk0FKjZuURHiiOJxQnSGfQJuoEKREejK0dFRGjoiQt7iOuLx0rgxYEQkAIfkECQoABwAsAAAAACEAIwAAA7h4utxnxslJDSGR6nrz/owxYB64QUEwlGaVqlB7vrAJscsd3Lhy+wBArGEICo3DUFH4QDqK0GMy51xOgcGlEAfJ+iAFie62chR+jYKaSAuQGOqwJp7jGQRDuol+F/jxZWsyCmoQfwYwgoM5Oyg1i2w0A2WQIW2TPYOIkleQmy+UlYygoaIPnJmapKmqKiusMmSdpjxypnALtrcHioq3ury7hGm3dnVosVpMWFmwREZbddDOSsjVswcJACH5BAkKAAcALAAAAAAhACMAAAOxeLrc/jDKSZUxNS9DCNYV54HURQwfGRlDEFwqdLVuGjOsW9/Odb0wnsUAKBKNwsMFQGwyNUHckVl8bqI4o43lA26PNkv1S9DtNuOeVirw+aTI3qWAQwnud1vhLSnQLS0GeFF+GoVKNF0fh4Z+LDQ6Bn5/MTNmL0mAl2E3j2aclTmRmYCQoKEDiaRDKFhJez6UmbKyQowHtzy1uEl8DLCnEktrQ2PBD1NxSlXKIW5hz6cJACH5BAkKAAcALAAAAAAhACMAAAOkeLrc/jDKSau9OOvNlTFd9H3hYxAEWDJfkK5LGwTq+g0zDR/GgM+10A04Cm56OANgqTRmkDTmSOiLMgFOTM9AnFJHuexzYBAIijZf2SweJ8ttbbXLmd5+wBiJosSCoGF/fXEeS1g8gHl9hxODKkh4gkwVIwUekESIhA4FlgV3PyCWG52WI2oGnR2lnUWpqhqVEF4Xi7QjhpsshpOFvLosrnpoEAkAIfkECQoABwAsAAAAACEAIwAAA6l4utz+MMpJq71YGpPr3t1kEAQXQltQnk8aBCa7bMMLy4wx1G8s072PL6SrGQDI4zBThCU/v50zCVhidIYgNPqxWZkDg0AgxB2K4vEXbBSvr1JtZ3uOext0x7FqovF6OXtfe1UzdjAxhINPM013ChtJER8FBQeVRX8GlpggFZWWfjwblTiigGZnfqRmpUKbljKxDrNMeY2eF4R8jUiSur6/Z8GFV2WBtwwJACH5BAkKAAcALAAAAAAhACMAAAO6eLrcZi3KyQwhkGpq8f6ONWQgaAxB8JTfg6YkO50pzD5xhaurhCsGAKCnEw6NucNDCAkyI8ugdAhFKpnJJdMaeiofBejowUseCr9GYa0j1GyMdVgjBxoEuPSZXWKf7gKBeHtzMms0gHgGfDIVLztmjScvNZEyk28qjT40b5aXlHCbDgOhnzedoqOOlKeopaqrCy56sgtotbYKhYW6e7e9tsHBssO6eSTIm1peV0iuFUZDyU7NJnmcuQsJACH5BAkKAAcALAAAAAAhACMAAAOteLrc/jDKSZsxNS9DCNYV54Hh4H0kdAXBgKaOwbYX/Miza1vrVe8KA2AoJL5gwiQgeZz4GMXlcHl8xozQ3kW3KTajL9zsBJ1+sV2fQfALem+XAlRApxu4ioI1UpC76zJ4fRqDBzI+LFyFhH1iiS59fkgziW07jjRAG5QDeECOLk2Tj6KjnZafW6hAej6Smgevr6yysza2tiCuMasUF2Yov2gZUUQbU8YaaqjLpQkAOw==' TEST_PNG_LOGO = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAjCAYAAAAaLGNkAAAAAXNSR0IB2cksfwAAAdVpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpDb21wcmVzc2lvbj4xPC90aWZmOkNvbXByZXNzaW9uPgogICAgICAgICA8dGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPjI8L3RpZmY6UGhvdG9tZXRyaWNJbnRlcnByZXRhdGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cjl0tmoAAAHVSURBVFgJ7VZRsoMgDNTOu5E9U+/Ud6Z6JssGNg2oNKD90xkHCNnNkgTbYbieKwNXBn6bgSXQ4+16xi5UDiqDN3Pecr6+1fM5DHh7n1NEIPjjoRLKzOjG3qQ5dRtEy2LCjh/Gz2wDZE2nZYKkrxdn/kY9XQQkGCGqqDY5IgJFkEKgBCzDNGXhTKEye7boFRH6IPJj5EshiNCSjV4R4eSx7zhmR2tcdIuwmWiMeao7e0JHViZEWUI5aP8a9O+rx74D6sGEiJftiX3YeueIiFXg2KrhpqzjVC3dPZFYJZ7NOwwtNwM8R0UkLfH0sT5qck+OlkMq0BucKr0iWG7gpAQksD9esM1z3Lnf6SHjLh67nnKEGxC/iomWhByTeXOQJGHHcKxwHhHKnt1HIdYtmexkIb/HOURWTSJqn2gKMDG0bDUc/D0iAseovxUBoylmQCug6IVhSv+4DIeKI94jAr4AjiSEgQ25JYB+YWT9BZ94AM8erwgFkRifaArA6U0G5KT0m//z26REZuK9okgrT6VwE1jTHjbVzyNAyRwTEPOtuiex9FVBNZCkruaA4PZqFp1u8Rpww9/6rcK5y0EkAxRiZJt79PWOVYWGRE9pbJhavMengMflGyumk0akMsQnAAAAAElFTkSuQmCC' @@ -183,3 +188,58 @@ def test_ui_settings(get, put, patch, delete, admin, enterprise_license): response = get(url, user=admin, expect=200) assert not response.data['CUSTOM_LOGO'] assert not response.data['CUSTOM_LOGIN_INFO'] + + +@pytest.mark.django_db +def test_logging_aggregrator_connection_test_requires_superuser(get, post, alice): + url = reverse('api:setting_logging_test') + post(url, {}, user=alice, expect=403) + + +@pytest.mark.parametrize('key', [ + 'LOG_AGGREGATOR_TYPE', + 'LOG_AGGREGATOR_HOST', + 'LOG_AGGREGATOR_PORT', +]) +@pytest.mark.django_db +def test_logging_aggregrator_connection_test_bad_request(get, post, admin, key): + url = reverse('api:setting_logging_test') + resp = post(url, {}, user=admin, expect=400) + assert 'This field is required.' in resp.data.get(key, []) + + +@pytest.mark.django_db +def test_logging_aggregrator_connection_test_valid(mocker, get, post, admin): + with mock.patch.object(BaseHTTPSHandler, 'perform_test') as perform_test: + url = reverse('api:setting_logging_test') + post(url, { + 'LOG_AGGREGATOR_TYPE': 'logstash', + 'LOG_AGGREGATOR_HOST': 'localhost', + 'LOG_AGGREGATOR_PORT': 8080, + 'LOG_AGGREGATOR_USERNAME': 'logger', + 'LOG_AGGREGATOR_PASSWORD': 'mcstash' + }, user=admin, expect=200) + perform_test.assert_called_with(OrderedDict([ + ('LOG_AGGREGATOR_HOST', u'localhost'), + ('LOG_AGGREGATOR_PORT', 8080), + ('LOG_AGGREGATOR_TYPE', 'logstash'), + ('LOG_AGGREGATOR_USERNAME', 'logger'), + ('LOG_AGGREGATOR_PASSWORD', 'mcstash'), + ('LOG_AGGREGATOR_LOGGERS', ['awx', 'activity_stream', 'job_events', 'system_tracking']), + ('LOG_AGGREGATOR_INDIVIDUAL_FACTS', False), + ('LOG_AGGREGATOR_ENABLED', False), + ('LOG_AGGREGATOR_TOWER_UUID', '') + ])) + + +@pytest.mark.django_db +def test_logging_aggregrator_connection_test_invalid(mocker, get, post, admin): + with mock.patch.object(BaseHTTPSHandler, 'perform_test') as perform_test: + perform_test.side_effect = LoggingConnectivityException('404: Not Found') + url = reverse('api:setting_logging_test') + resp = post(url, { + 'LOG_AGGREGATOR_TYPE': 'logstash', + 'LOG_AGGREGATOR_HOST': 'localhost', + 'LOG_AGGREGATOR_PORT': 8080 + }, user=admin, expect=500) + assert resp.data == {'error': '404: Not Found'} diff --git a/awx/main/tests/unit/utils/test_handlers.py b/awx/main/tests/unit/utils/test_handlers.py index 3de3b2e7b7..0f4dbdff05 100644 --- a/awx/main/tests/unit/utils/test_handlers.py +++ b/awx/main/tests/unit/utils/test_handlers.py @@ -1,13 +1,15 @@ import base64 import json import logging +from uuid import uuid4 from django.conf import LazySettings import pytest import requests from requests_futures.sessions import FuturesSession -from awx.main.utils.handlers import BaseHTTPSHandler as HTTPSHandler, PARAM_NAMES +from awx.main.utils.handlers import (BaseHTTPSHandler as HTTPSHandler, + PARAM_NAMES, LoggingConnectivityException) from awx.main.utils.formatters import LogstashFormatter @@ -25,19 +27,21 @@ def dummy_log_record(): @pytest.fixture() -def ok200_adapter(): - class OK200Adapter(requests.adapters.HTTPAdapter): +def http_adapter(): + class FakeHTTPAdapter(requests.adapters.HTTPAdapter): requests = [] + status = 200 + reason = None def send(self, request, **kwargs): self.requests.append(request) resp = requests.models.Response() - resp.status_code = 200 - resp.raw = '200 OK' + resp.status_code = self.status + resp.reason = self.reason resp.request = request return resp - return OK200Adapter() + return FakeHTTPAdapter() def test_https_logging_handler_requests_sync_implementation(): @@ -73,6 +77,42 @@ def test_https_logging_handler_from_django_settings(param, django_settings_name) assert hasattr(handler, param) and getattr(handler, param) == 'EXAMPLE' +@pytest.mark.parametrize( + 'status, reason, exc', + [(200, '200 OK', None), (404, 'Not Found', LoggingConnectivityException)] +) +def test_https_logging_handler_connectivity_test(http_adapter, status, reason, exc): + http_adapter.status = status + http_adapter.reason = reason + settings = LazySettings() + settings.configure(**{ + 'LOG_AGGREGATOR_HOST': 'example.org', + 'LOG_AGGREGATOR_PORT': 8080, + 'LOG_AGGREGATOR_TYPE': 'logstash', + 'LOG_AGGREGATOR_USERNAME': 'user', + 'LOG_AGGREGATOR_PASSWORD': 'password', + 'LOG_AGGREGATOR_LOGGERS': ['awx', 'activity_stream', 'job_events', 'system_tracking'], + 'CLUSTER_HOST_ID': '', + 'LOG_AGGREGATOR_TOWER_UUID': str(uuid4()) + }) + + class FakeHTTPSHandler(HTTPSHandler): + + def __init__(self, *args, **kwargs): + super(FakeHTTPSHandler, self).__init__(*args, **kwargs) + self.session.mount('http://', http_adapter) + + def emit(self, record): + return super(FakeHTTPSHandler, self).emit(record) + + if exc: + with pytest.raises(exc) as e: + FakeHTTPSHandler.perform_test(settings) + assert str(e).endswith('%s: %s' % (status, reason)) + else: + assert FakeHTTPSHandler.perform_test(settings) is None + + def test_https_logging_handler_logstash_auth_info(): handler = HTTPSHandler(message_type='logstash', username='bob', password='ansible') handler.add_auth_information() @@ -120,19 +160,19 @@ def test_https_logging_handler_skip_log(params, logger_name, expected): ('splunk', False), ('splunk', True), ]) -def test_https_logging_handler_emit(ok200_adapter, dummy_log_record, +def test_https_logging_handler_emit(http_adapter, dummy_log_record, message_type, async): handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True, message_type=message_type, enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'], async=async) handler.setFormatter(LogstashFormatter()) - handler.session.mount('http://', ok200_adapter) + handler.session.mount('http://', http_adapter) async_futures = handler.emit(dummy_log_record) [future.result() for future in async_futures] - assert len(ok200_adapter.requests) == 1 - request = ok200_adapter.requests[0] + assert len(http_adapter.requests) == 1 + request = http_adapter.requests[0] assert request.url == 'http://127.0.0.1/' assert request.method == 'POST' body = json.loads(request.body) @@ -152,7 +192,7 @@ def test_https_logging_handler_emit(ok200_adapter, dummy_log_record, @pytest.mark.parametrize('async', (True, False)) -def test_https_logging_handler_emit_logstash_with_creds(ok200_adapter, +def test_https_logging_handler_emit_logstash_with_creds(http_adapter, dummy_log_record, async): handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True, username='user', password='pass', @@ -160,38 +200,38 @@ def test_https_logging_handler_emit_logstash_with_creds(ok200_adapter, enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'], async=async) handler.setFormatter(LogstashFormatter()) - handler.session.mount('http://', ok200_adapter) + handler.session.mount('http://', http_adapter) async_futures = handler.emit(dummy_log_record) [future.result() for future in async_futures] - assert len(ok200_adapter.requests) == 1 - request = ok200_adapter.requests[0] + assert len(http_adapter.requests) == 1 + request = http_adapter.requests[0] assert request.headers['Authorization'] == 'Basic %s' % base64.b64encode("user:pass") @pytest.mark.parametrize('async', (True, False)) -def test_https_logging_handler_emit_splunk_with_creds(ok200_adapter, +def test_https_logging_handler_emit_splunk_with_creds(http_adapter, dummy_log_record, async): handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True, password='pass', message_type='splunk', enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'], async=async) handler.setFormatter(LogstashFormatter()) - handler.session.mount('http://', ok200_adapter) + handler.session.mount('http://', http_adapter) async_futures = handler.emit(dummy_log_record) [future.result() for future in async_futures] - assert len(ok200_adapter.requests) == 1 - request = ok200_adapter.requests[0] + assert len(http_adapter.requests) == 1 + request = http_adapter.requests[0] assert request.headers['Authorization'] == 'Splunk pass' -def test_https_logging_handler_emit_one_record_per_fact(ok200_adapter): +def test_https_logging_handler_emit_one_record_per_fact(http_adapter): handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True, message_type='logstash', indv_facts=True, enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking']) handler.setFormatter(LogstashFormatter()) - handler.session.mount('http://', ok200_adapter) + handler.session.mount('http://', http_adapter) record = logging.LogRecord( 'awx.analytics.system_tracking', # logger name 20, # loglevel INFO @@ -212,8 +252,8 @@ def test_https_logging_handler_emit_one_record_per_fact(ok200_adapter): async_futures = handler.emit(record) [future.result() for future in async_futures] - assert len(ok200_adapter.requests) == 2 - requests = sorted(ok200_adapter.requests, key=lambda request: json.loads(request.body)['version']) + assert len(http_adapter.requests) == 2 + requests = sorted(http_adapter.requests, key=lambda request: json.loads(request.body)['version']) request = requests[0] assert request.url == 'http://127.0.0.1/' diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py index fe2fb87228..a258b8794c 100644 --- a/awx/main/utils/handlers.py +++ b/awx/main/utils/handlers.py @@ -5,6 +5,7 @@ import logging import json import requests +from requests.exceptions import RequestException from copy import copy # loggly @@ -40,6 +41,10 @@ def unused_callback(sess, resp): pass +class LoggingConnectivityException(Exception): + pass + + class HTTPSNullHandler(logging.NullHandler): "Placeholder null handler to allow loading without database access" @@ -66,6 +71,31 @@ class BaseHTTPSHandler(logging.Handler): kwargs[param] = getattr(settings, django_setting_name, None) return cls(*args, **kwargs) + @classmethod + def perform_test(cls, settings): + """ + Tests logging connectivity for the current logging settings. + @raises LoggingConnectivityException + """ + handler = cls.from_django_settings(settings, async=True) + handler.enabled_flag = True + handler.setFormatter(LogstashFormatter(settings_module=settings)) + logger = logging.getLogger(__file__) + fn, lno, func = logger.findCaller() + record = logger.makeRecord('awx', 10, fn, lno, + 'Ansible Tower Connection Test', tuple(), + None, func) + futures = handler.emit(record) + for future in futures: + try: + resp = future.result() + if not resp.ok: + raise LoggingConnectivityException( + ': '.join([str(resp.status_code), resp.reason or '']) + ) + except RequestException as e: + raise LoggingConnectivityException(str(e)) + def get_full_message(self, record): if record.exc_info: return '\n'.join(traceback.format_exception(*record.exc_info)) From cc65ad3828eef68325078c3f7a9fd59b93f255c9 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Thu, 9 Mar 2017 13:20:04 -0500 Subject: [PATCH 31/39] Added tooltips to workflow nodes with long resource names --- .../workflow-chart/workflow-chart.block.less | 20 +++++++++++ .../workflow-chart.directive.js | 33 +++++++++++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less index bdb280e963..b12adf5d26 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less @@ -114,3 +114,23 @@ padding: 1px 3px; border-radius: 4px; } +.WorkflowChart-nameText { + font-size: 10px; +} + +.WorkflowChart-tooltipContents { + padding: 10px; + background-color: #707070; + color: #FFFFFF; + border-radius: 4px; + word-wrap: break-word; + max-width: 325px; +} +.WorkflowChart-tooltipArrow { + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid #707070; + margin: auto; +} diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index e7379fe19c..fe62994d7b 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -23,12 +23,13 @@ export default [ '$state','moment', '$timeout', '$window', let margin = {top: 20, right: 20, bottom: 20, left: 20}, i = 0, - nodeW = 120, + nodeW = 180, nodeH = 60, rootW = 60, rootH = 40, startNodeOffsetY = scope.mode === 'details' ? 17 : 10, verticalSpaceBetweenNodes = 20, + maxNodeTextLength = 27, windowHeight, windowWidth, tree, @@ -125,11 +126,8 @@ export default [ '$state','moment', '$timeout', '$window', // TODO: this function is hacky and we need to come up with a better solution // see: http://stackoverflow.com/questions/15975440/add-ellipses-to-overflowing-text-in-svg#answer-27723752 function wrap(text) { - - let maxLength = scope.mode === 'details' ? 14 : 15; - - if(text && text.length > maxLength) { - return text.substring(0,maxLength) + '...'; + if(text && text.length > maxNodeTextLength) { + return text.substring(0,maxNodeTextLength) + '...'; } else { return text; @@ -216,7 +214,7 @@ export default [ '$state','moment', '$timeout', '$window', links = tree.links(nodes); let node = svgGroup.selectAll("g.node") .data(nodes, function(d) { - d.y = d.depth * 180; + d.y = d.depth * 240; return d.id || (d.id = ++i); }); @@ -347,11 +345,32 @@ export default [ '$state','moment', '$timeout', '$window', .call(edit_node) .on("mouseover", function(d) { if(!d.isStartNode) { + let resourceName = (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : ""; + if(resourceName && resourceName.length > maxNodeTextLength) { + // Render the tooltip quickly in the dom and then remove. This lets us know how big the tooltip is so that we can place + // it properly on the workflow + let tooltipDimensionChecker = $(""); + $('body').append(tooltipDimensionChecker); + let tipWidth = $(tooltipDimensionChecker).outerWidth(); + let tipHeight = $(tooltipDimensionChecker).outerHeight(); + $(tooltipDimensionChecker).remove(); + + thisNode.append("foreignObject") + .attr("x", (nodeW / 2) - (tipWidth / 2)) + .attr("y", (tipHeight + 15) * -1) + .attr("width", tipWidth) + .attr("height", tipHeight+20) + .attr("class", "WorkflowChart-tooltip") + .html(function(){ + return "
" + resourceName + "
"; + }); + } d3.select("#node-" + d.id) .classed("hovering", true); } }) .on("mouseout", function(d){ + $('.WorkflowChart-tooltip').remove(); if(!d.isStartNode) { d3.select("#node-" + d.id) .classed("hovering", false); From 9bc162cadaaa74e5009fd9346ccc4cac889cad9d Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Thu, 9 Mar 2017 16:14:38 -0500 Subject: [PATCH 32/39] start a CHANGELOG --- docs/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/CHANGELOG.md diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000000..831959c46f --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,5 @@ +3.2.0 +===== +* added a new API endpoint - `/api/v1/settings/logging/test/` - for testing + external log aggregrator connectivity + [[#5164](https://github.com/ansible/ansible-tower/issues/5164)] From 674b971a5f0b547d3d700971c658dfb7b5278e74 Mon Sep 17 00:00:00 2001 From: Ben Thomasson Date: Thu, 9 Mar 2017 17:36:28 -0500 Subject: [PATCH 33/39] Fixes #5675. Search key padding audit. --- awx/ui/client/src/shared/smart-search/smart-search.block.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/shared/smart-search/smart-search.block.less b/awx/ui/client/src/shared/smart-search/smart-search.block.less index 3163ec945b..bd295d5b0d 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.block.less +++ b/awx/ui/client/src/shared/smart-search/smart-search.block.less @@ -160,7 +160,7 @@ } .SmartSearch-keyToggle { margin-right: auto; - margin-left: 12px; + margin-left: 20px; text-transform: uppercase; background-color: @default-bg; border-radius: 5px; @@ -192,7 +192,7 @@ font-size: 12px; width: 100%; padding: 15px; - margin-bottom: 15px; + margin-bottom: 20px; border-radius: 4px; border: 1px solid @login-notice-border; background-color: @login-notice-bg; From 0970726fa6a2afc592cd13c16a4203e173b91228 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 9 Mar 2017 18:53:21 -0500 Subject: [PATCH 34/39] Don't pre-build UI deps for unit tests I know, this sucks. I spent all day trying to get to the bottom of the CI failures that started happening the other day with no luck. There is something going on with how we were moving the node_modules directory into the source tree from the pre-built location in /tmp. This was working, but then it broke. I hope to cycle back on this sometime next week if I have the time. --- tools/docker-compose/unit-tests/Dockerfile | 19 ++----------------- tools/docker-compose/unit-tests/entrypoint | 7 ------- 2 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 tools/docker-compose/unit-tests/entrypoint diff --git a/tools/docker-compose/unit-tests/Dockerfile b/tools/docker-compose/unit-tests/Dockerfile index d1fec4e583..2de3715f7b 100644 --- a/tools/docker-compose/unit-tests/Dockerfile +++ b/tools/docker-compose/unit-tests/Dockerfile @@ -1,26 +1,11 @@ FROM gcr.io/ansible-tower-engineering/tower_devel:latest +# For UI tests RUN yum install -y bzip2 gcc-c++ -# We need to install dependencies somewhere other than /ansible-tower. -# Anything in /ansible-tower will be overwritten by the bind-mount. -# We switch the WORKDIR to /ansible-tower further down. -WORKDIR "/tmp/" - -COPY awx/ui/package.json awx/ui/ - RUN npm set progress=false -# Copy __init__.py so the Makefile can retrieve `awx.__version__` -COPY awx/__init__.py awx/ -RUN make ui-deps - WORKDIR "/tower_devel" -# This entrypoint script takes care of moving the node_modules -# into the bind-mount, then exec's to whatever was passed as the CMD. -ADD tools/docker-compose/unit-tests/entrypoint /usr/bin/ -RUN chmod +x /usr/bin/entrypoint - -ENTRYPOINT ["entrypoint"] +ENTRYPOINT ["/bin/bash", "-c"] CMD ["bash"] diff --git a/tools/docker-compose/unit-tests/entrypoint b/tools/docker-compose/unit-tests/entrypoint deleted file mode 100644 index cb7fbe142e..0000000000 --- a/tools/docker-compose/unit-tests/entrypoint +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -set -e - -mkdir -p /tower_devel/awx/ui/ -mv -n /tmp/awx/ui/node_modules /tmp/awx/ui/.deps_built /tower_devel/awx/ui - -exec $@ From 323e8ad8f0f270b9287bbcc800ba496599aaec44 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 9 Mar 2017 18:53:42 -0500 Subject: [PATCH 35/39] Add awx/ui/coverage to clean target --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index fb5f22c313..2d155223c0 100644 --- a/Makefile +++ b/Makefile @@ -215,6 +215,7 @@ clean-bundle: clean-ui: rm -rf awx/ui/static/ rm -rf awx/ui/node_modules/ + rm -rf awx/ui/coverage/ rm -f $(UI_DEPS_FLAG_FILE) rm -f $(UI_RELEASE_FLAG_FILE) From 053ecd1093e025c1e3ac62a7f7ec12bd884b91e2 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 9 Mar 2017 19:06:13 -0500 Subject: [PATCH 36/39] Make sure ui deps are built for jshint --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2d155223c0..c3511d5dbf 100644 --- a/Makefile +++ b/Makefile @@ -609,7 +609,7 @@ ui-test-ci: $(UI_DEPS_FLAG_FILE) testjs_ci: echo "Update UI unittests later" #ui-test-ci -jshint: +jshint: $(UI_DEPS_FLAG_FILE) $(NPM_BIN) run --prefix awx/ui jshint ui-test-saucelabs: $(UI_DEPS_FLAG_FILE) From 02a86c1d96751d1d688e87805bdd290d0a11da14 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 9 Mar 2017 18:53:21 -0500 Subject: [PATCH 37/39] Don't pre-build UI deps for unit tests I know, this sucks. I spent all day trying to get to the bottom of the CI failures that started happening the other day with no luck. There is something going on with how we were moving the node_modules directory into the source tree from the pre-built location in /tmp. This was working, but then it broke. I hope to cycle back on this sometime next week if I have the time. --- tools/docker-compose/unit-tests/Dockerfile | 19 ++----------------- tools/docker-compose/unit-tests/entrypoint | 7 ------- 2 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 tools/docker-compose/unit-tests/entrypoint diff --git a/tools/docker-compose/unit-tests/Dockerfile b/tools/docker-compose/unit-tests/Dockerfile index d1fec4e583..2de3715f7b 100644 --- a/tools/docker-compose/unit-tests/Dockerfile +++ b/tools/docker-compose/unit-tests/Dockerfile @@ -1,26 +1,11 @@ FROM gcr.io/ansible-tower-engineering/tower_devel:latest +# For UI tests RUN yum install -y bzip2 gcc-c++ -# We need to install dependencies somewhere other than /ansible-tower. -# Anything in /ansible-tower will be overwritten by the bind-mount. -# We switch the WORKDIR to /ansible-tower further down. -WORKDIR "/tmp/" - -COPY awx/ui/package.json awx/ui/ - RUN npm set progress=false -# Copy __init__.py so the Makefile can retrieve `awx.__version__` -COPY awx/__init__.py awx/ -RUN make ui-deps - WORKDIR "/tower_devel" -# This entrypoint script takes care of moving the node_modules -# into the bind-mount, then exec's to whatever was passed as the CMD. -ADD tools/docker-compose/unit-tests/entrypoint /usr/bin/ -RUN chmod +x /usr/bin/entrypoint - -ENTRYPOINT ["entrypoint"] +ENTRYPOINT ["/bin/bash", "-c"] CMD ["bash"] diff --git a/tools/docker-compose/unit-tests/entrypoint b/tools/docker-compose/unit-tests/entrypoint deleted file mode 100644 index cb7fbe142e..0000000000 --- a/tools/docker-compose/unit-tests/entrypoint +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -set -e - -mkdir -p /tower_devel/awx/ui/ -mv -n /tmp/awx/ui/node_modules /tmp/awx/ui/.deps_built /tower_devel/awx/ui - -exec $@ From 53ef8072b82fbadcfba63f3f772f3e29e5f2aa6d Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 9 Mar 2017 18:53:42 -0500 Subject: [PATCH 38/39] Add awx/ui/coverage to clean target --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 836f96da9c..a008488e88 100644 --- a/Makefile +++ b/Makefile @@ -215,6 +215,7 @@ clean-bundle: clean-ui: rm -rf awx/ui/static/ rm -rf awx/ui/node_modules/ + rm -rf awx/ui/coverage/ rm -f $(UI_DEPS_FLAG_FILE) rm -f $(UI_RELEASE_FLAG_FILE) From 87e6d12abb2517847aee9eb290680ee5c1363545 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 9 Mar 2017 19:06:13 -0500 Subject: [PATCH 39/39] Make sure ui deps are built for jshint --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a008488e88..0d6aaae3db 100644 --- a/Makefile +++ b/Makefile @@ -609,7 +609,7 @@ ui-test-ci: $(UI_DEPS_FLAG_FILE) testjs_ci: echo "Update UI unittests later" #ui-test-ci -jshint: +jshint: $(UI_DEPS_FLAG_FILE) $(NPM_BIN) run --prefix awx/ui jshint ui-test-saucelabs: $(UI_DEPS_FLAG_FILE)