From a9e0de98d7866357fbb463a646911cce6a52d362 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 4 Feb 2015 09:38:54 -0500 Subject: [PATCH] Use broccoli to build app --- .gitignore | 5 + .jshintrc | 1 + Brocfile.js | 64 +- awx/ui/static/js/app.js | 155 +- awx/ui/static/js/controllers/Admins.js | 2 +- .../static/js/controllers/Authentication.js | 2 +- awx/ui/static/js/controllers/Credentials.js | 6 +- awx/ui/static/js/controllers/Home.js | 8 +- awx/ui/static/js/controllers/Inventories.js | 6 +- awx/ui/static/js/controllers/JobDetail.js | 2 +- awx/ui/static/js/controllers/JobEvents.js | 4 +- awx/ui/static/js/controllers/JobHosts.js | 2 +- awx/ui/static/js/controllers/JobStdout.js | 2 +- awx/ui/static/js/controllers/JobTemplates.js | 6 +- awx/ui/static/js/controllers/Jobs.js | 2 +- awx/ui/static/js/controllers/Organizations.js | 6 +- awx/ui/static/js/controllers/Permissions.js | 6 +- awx/ui/static/js/controllers/Portal.js | 2 +- awx/ui/static/js/controllers/Projects.js | 6 +- awx/ui/static/js/controllers/Schedules.js | 2 +- awx/ui/static/js/controllers/Sockets.js | 2 +- awx/ui/static/js/controllers/Teams.js | 6 +- awx/ui/static/js/controllers/Users.js | 6 +- .../static/js/directives/_dashboard-graphs.js | 13 +- .../static/js/directives/auto-size-module.js | 10 +- .../static/js/directives/host-count-graph.js | 21 +- .../static/js/directives/host-status-graph.js | 12 +- .../static/js/directives/job-status-graph.js | 17 +- awx/ui/static/js/forms.js | 58 + awx/ui/static/js/forms/ActivityDetail.js | 54 +- awx/ui/static/js/forms/Credentials.js | 848 ++-- awx/ui/static/js/forms/CustomInventory.js | 116 +- awx/ui/static/js/forms/EventsViewer.js | 140 +- awx/ui/static/js/forms/Groups.js | 96 +- awx/ui/static/js/forms/HostGroups.js | 76 +- awx/ui/static/js/forms/Hosts.js | 164 +- awx/ui/static/js/forms/Inventories.js | 152 +- awx/ui/static/js/forms/InventoryStatus.js | 91 +- awx/ui/static/js/forms/JobEventData.js | 32 +- awx/ui/static/js/forms/JobSummary.js | 75 +- awx/ui/static/js/forms/JobTemplates.js | 724 ++-- awx/ui/static/js/forms/JobVarsPrompt.js | 42 +- awx/ui/static/js/forms/Jobs.js | 200 +- awx/ui/static/js/forms/LicenseForm.js | 129 +- awx/ui/static/js/forms/LicenseUpdate.js | 74 +- awx/ui/static/js/forms/LogViewerOptions.js | 174 +- awx/ui/static/js/forms/LogViewerStatus.js | 88 +- awx/ui/static/js/forms/Organizations.js | 270 +- awx/ui/static/js/forms/Permissions.js | 238 +- awx/ui/static/js/forms/ProjectStatus.js | 70 +- awx/ui/static/js/forms/Projects.js | 4 +- awx/ui/static/js/forms/Source.js | 476 +-- awx/ui/static/js/forms/SurveyMaker.js | 102 +- awx/ui/static/js/forms/SurveyQuestion.js | 506 +-- awx/ui/static/js/forms/Teams.js | 466 +-- awx/ui/static/js/forms/Users.js | 514 +-- awx/ui/static/js/help.js | 11 + awx/ui/static/js/helpers.js | 82 + awx/ui/static/js/helpers/AboutAnsible.js | 127 +- awx/ui/static/js/helpers/Access.js | 92 +- awx/ui/static/js/helpers/Children.js | 172 +- awx/ui/static/js/helpers/ConfigureTower.js | 866 ++-- awx/ui/static/js/helpers/Credentials.js | 478 ++- awx/ui/static/js/helpers/CustomInventory.js | 620 +-- awx/ui/static/js/helpers/EventViewer.js | 1018 ++--- awx/ui/static/js/helpers/Events.js | 388 +- awx/ui/static/js/helpers/Groups.js | 3680 +++++++++-------- awx/ui/static/js/helpers/HostEventsViewer.js | 512 +-- awx/ui/static/js/helpers/Hosts.js | 2302 +++++------ awx/ui/static/js/helpers/JobDetail.js | 2572 ++++++------ awx/ui/static/js/helpers/JobSubmission.js | 1957 ++++----- awx/ui/static/js/helpers/JobTemplates.js | 319 +- awx/ui/static/js/helpers/Jobs.js | 1084 ++--- awx/ui/static/js/helpers/License.js | 1005 ++--- awx/ui/static/js/helpers/LoadConfig.js | 1 + awx/ui/static/js/helpers/LogViewer.js | 680 +-- awx/ui/static/js/helpers/Lookup.js | 388 +- awx/ui/static/js/helpers/PaginationHelpers.js | 290 +- awx/ui/static/js/helpers/Parse.js | 154 +- awx/ui/static/js/helpers/Permissions.js | 80 +- awx/ui/static/js/helpers/ProjectPath.js | 130 +- awx/ui/static/js/helpers/Projects.js | 116 +- awx/ui/static/js/helpers/Schedules.js | 1122 ++--- awx/ui/static/js/helpers/Selection.js | 288 +- awx/ui/static/js/helpers/SocketHelper.js | 54 +- awx/ui/static/js/helpers/Survey.js | 4 +- awx/ui/static/js/helpers/Users.js | 56 +- awx/ui/static/js/helpers/Variables.js | 3 +- awx/ui/static/js/helpers/api-defaults.js | 146 +- awx/ui/static/js/helpers/inventory.js | 698 ++-- awx/ui/static/js/helpers/md5.js | 50 +- awx/ui/static/js/helpers/refresh-related.js | 62 +- awx/ui/static/js/helpers/refresh.js | 86 +- awx/ui/static/js/helpers/related-search.js | 448 +- awx/ui/static/js/helpers/search.js | 984 ++--- awx/ui/static/js/helpers/teams.js | 214 +- awx/ui/static/js/lists.js | 61 + awx/ui/static/js/lists/Admins.js | 3 +- awx/ui/static/js/lists/CloudCredentials.js | 3 +- awx/ui/static/js/lists/CompletedJobs.js | 3 +- awx/ui/static/js/lists/ConfigureTowerJobs.js | 3 +- awx/ui/static/js/lists/Credentials.js | 3 +- awx/ui/static/js/lists/CustomInventory.js | 3 +- awx/ui/static/js/lists/Groups.js | 3 +- awx/ui/static/js/lists/HomeGroups.js | 3 +- awx/ui/static/js/lists/HomeHosts.js | 3 +- awx/ui/static/js/lists/Hosts.js | 3 +- awx/ui/static/js/lists/Inventories.js | 3 +- awx/ui/static/js/lists/InventoryGroups.js | 3 +- awx/ui/static/js/lists/InventoryHosts.js | 3 +- awx/ui/static/js/lists/JobEvents.js | 3 +- awx/ui/static/js/lists/JobHosts.js | 3 +- awx/ui/static/js/lists/JobTemplates.js | 3 +- awx/ui/static/js/lists/Jobs.js | 3 +- awx/ui/static/js/lists/Organizations.js | 3 +- awx/ui/static/js/lists/Permissions.js | 3 +- awx/ui/static/js/lists/PortalJobTemplates.js | 3 +- awx/ui/static/js/lists/PortalJobs.js | 3 +- awx/ui/static/js/lists/Projects.js | 3 +- awx/ui/static/js/lists/QueuedJobs.js | 3 +- awx/ui/static/js/lists/RunningJobs.js | 3 +- awx/ui/static/js/lists/ScheduledJobs.js | 3 +- awx/ui/static/js/lists/Schedules.js | 3 +- awx/ui/static/js/lists/Streams.js | 3 +- awx/ui/static/js/lists/Teams.js | 3 +- awx/ui/static/js/lists/Users.js | 3 +- awx/ui/static/js/services/_data-services.js | 8 +- .../static/js/services/adjust-graph-size.js | 5 +- .../js/services/host-count-graph-data.js | 87 +- .../js/services/job-status-graph-data.js | 111 +- awx/ui/static/js/widgets.js | 25 + awx/ui/static/lib/angular-md5/angular-md5.js | 2 +- awx/ui/static/lib/d3/.bower.json | 36 + awx/ui/static/lib/d3/CONTRIBUTING.md | 25 + awx/ui/static/lib/{d3js/build => d3}/LICENSE | 2 +- awx/ui/static/lib/d3/README.md | 9 + awx/ui/static/lib/d3/bower.json | 26 + .../lib/{d3js/build/d3.v3.js => d3/d3.js} | 3217 +++++++------- awx/ui/static/lib/d3/d3.min.js | 5 + awx/ui/static/lib/d3js/.bower.json | 14 - awx/ui/static/lib/d3js/.gitignore | 15 - awx/ui/static/lib/d3js/LICENSE | 20 - awx/ui/static/lib/d3js/README.md | 2 - awx/ui/static/lib/d3js/build/d3.v3.min.js | 5 - awx/ui/static/lib/loader.js/.bower.json | 26 + awx/ui/static/lib/loader.js/README.md | 0 awx/ui/static/lib/loader.js/bower.json | 17 + awx/ui/static/lib/loader.js/loader.js | 110 + awx/ui/static/partials/survey_maker.html | 1 + awx/ui/templates/ui/index.html | 5 + 150 files changed, 17234 insertions(+), 16381 deletions(-) create mode 100644 awx/ui/static/js/forms.js create mode 100644 awx/ui/static/js/help.js create mode 100644 awx/ui/static/js/helpers.js create mode 100644 awx/ui/static/js/lists.js create mode 100644 awx/ui/static/js/widgets.js create mode 100644 awx/ui/static/lib/d3/.bower.json create mode 100644 awx/ui/static/lib/d3/CONTRIBUTING.md rename awx/ui/static/lib/{d3js/build => d3}/LICENSE (97%) mode change 100755 => 100644 create mode 100644 awx/ui/static/lib/d3/README.md create mode 100644 awx/ui/static/lib/d3/bower.json rename awx/ui/static/lib/{d3js/build/d3.v3.js => d3/d3.js} (82%) mode change 100755 => 100644 create mode 100644 awx/ui/static/lib/d3/d3.min.js delete mode 100644 awx/ui/static/lib/d3js/.bower.json delete mode 100644 awx/ui/static/lib/d3js/.gitignore delete mode 100644 awx/ui/static/lib/d3js/LICENSE delete mode 100644 awx/ui/static/lib/d3js/README.md delete mode 100755 awx/ui/static/lib/d3js/build/d3.v3.min.js create mode 100644 awx/ui/static/lib/loader.js/.bower.json create mode 100644 awx/ui/static/lib/loader.js/README.md create mode 100644 awx/ui/static/lib/loader.js/bower.json create mode 100644 awx/ui/static/lib/loader.js/loader.js diff --git a/.gitignore b/.gitignore index 5105eaac16..e9b00f53d3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,10 @@ tar-build /bower.json /package.json node_modules/** +/tmp + +# UI build debugging +/DEBUG-* # Testing .coverage @@ -63,3 +67,4 @@ setup/inventory env/* nohup.out reports + diff --git a/.jshintrc b/.jshintrc index 8562d25f2e..a69ddcc652 100644 --- a/.jshintrc +++ b/.jshintrc @@ -8,6 +8,7 @@ "latedef": "nofunc", "noarg": true, "nonew": true, + "maxerr": 10000, "notypeof": true, "globals": { "angular":false, diff --git a/Brocfile.js b/Brocfile.js index a3e7bd5bc9..fe818ec602 100644 --- a/Brocfile.js +++ b/Brocfile.js @@ -10,6 +10,8 @@ var concatFiles = require('broccoli-sourcemap-concat'); var compileLess = require('broccoli-less-single'); var gzip = require('broccoli-gzip'); var debug = require('broccoli-stew').debug; +var ES6 = require('broccoli-es6modules'); +var Funnel = require('broccoli-funnel'); // Get extra args after '--' var allArgs = parseArgs(process.argv.slice(2), { '--': true }); @@ -19,8 +21,15 @@ var shouldCompress = isUndefined(args.compress) ? false : args.compress; var debugMode = isUndefined(args.debug) ? false : args.debug; var silentMode = isUndefined(args.silent) ? false : args.silent; +var appName = 'tower'; + +if (debugMode) { + log('*** DEBUG MODE ***'); +} + var vendorFiles = - [ 'jquery/dist/jquery.js', + [ 'loader.js/loader.js', + 'jquery/dist/jquery.js', 'angular/angular.js', 'angular-route/angular-route.js', 'angular-resource/angular-resource.js', @@ -34,7 +43,7 @@ var vendorFiles = 'angular-animate/angular-animate.js', 'angular-tz-extensions/packages/jstimezonedetect/jstz.js', 'socket.io-client/dist/socket.io.js', - 'd3js/build/d3.v3.js', + 'd3/d3.js', 'novus-nvd3/nv.d3.js', 'angular-codemirror/lib/AngularCodeMirror.js', 'timezone-js/src/date.js', @@ -59,11 +68,21 @@ var vendorFiles = ]; function log() { - var msgs = Array.prototype.splice.apply(arguments); + var msgs = Array.prototype.slice.apply(arguments); if (!silentMode) { console.log.apply(null, msgs); } + + return msgs.slice(-1); +} + +function doDebug(name, tree) { + if (debugMode) { + tree = debug(tree, { name: name }); + } + + return tree; } function prependLibDir(file) { @@ -73,21 +92,44 @@ function prependLibDir(file) { vendorFiles = vendorFiles.map(prependLibDir); var root = 'awx/ui/static'; -var app = root; +var app = new Funnel(root, + { include: flatten([vendorFiles, 'js/**/*.js']) + }); -function log(msg, obj) { - console.log(msg + ":", obj); - return obj; -} +app = doDebug('initial-select', app); + +var appStyles = new Funnel(root, + { include: ['**/*.less'] + }); + +var applicationJS = new Funnel(app, + { include: ['**/*.js'], + srcDir: 'js', + destDir: appName + }); + +applicationJS = doDebug('app-funnel', applicationJS); + +var vendorJS = new Funnel(app, + { include: ['lib/**/*.js'] + }); + +vendorJS = doDebug('vendor-funnel', vendorJS); + +applicationJS = new ES6(applicationJS); + +app = mergeTrees([vendorJS, applicationJS]); + +app = doDebug('merged-app-vendor', app); app = concatFiles(app, { outputFile: 'tower.concat.js', - inputFiles: flatten([vendorFiles, ['js/**/*.js', 'js/app.js', 'js/config.js', 'js/local_config.js']]) + inputFiles: flatten([vendorFiles, ['tower/**/*.js', 'tower/app.js', 'tower/config.js', 'tower/local_config.js']]) }); -app = debug(app, {name: 'concat'}); +app = doDebug('concat', app); -var styles = compileLess(path.join(root, 'less'), 'ansible-ui.less', 'tower.min.css'); +var styles = compileLess(appStyles, 'less/ansible-ui.less', 'tower.min.css'); app = mergeTrees([app, styles]); diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 965ca96f6f..bda458f041 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -7,8 +7,7 @@ * */ -var urlPrefix, - $AnsibleConfig; +var urlPrefix; if ($basePath) { urlPrefix = $basePath; @@ -18,17 +17,46 @@ if ($basePath) { urlPrefix = $basePath; } +import * as Helpers from 'tower/helpers'; +import * as Forms from 'tower/forms'; +import * as Lists from 'tower/lists'; +import * as Widgets from 'tower/widgets'; +import * as Help from 'tower/help'; +import {Home, HomeGroups, HomeHosts} from 'tower/controllers/Home'; +import {SocketsController} from 'tower/controllers/Sockets'; +import {Authenticate} from 'tower/controllers/Authentication'; +import {CredentialsAdd, CredentialsEdit, CredentialsList} from 'tower/controllers/Credentials'; +import {JobsListController} from 'tower/controllers/Jobs'; +import {PortalController} from 'tower/controllers/Portal'; -angular.module('Tower', [ +import dataServices from 'tower/services/_data-services'; +import dashboardGraphs from 'tower/directives/_dashboard-graphs'; + + +import {JobDetailController} from 'tower/controllers/JobDetail'; +import {JobStdoutController} from 'tower/controllers/JobStdout'; +import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from 'tower/controllers/JobTemplates'; +import {ScheduleEditController} from 'tower/controllers/Schedules'; +import {ProjectsList, ProjectsAdd, ProjectsEdit} from 'tower/controllers/Projects'; +import {OrganizationsList, OrganizationsAdd, OrganizationsEdit} from 'tower/controllers/Organizations'; +import {InventoriesList, InventoriesAdd, InventoriesEdit} from 'tower/controllers/Inventories'; +import {AdminsList} from 'tower/controllers/Admins'; +import {UsersList, UsersAdd, UsersEdit} from 'tower/controllers/Users'; +import {TeamsList, TeamsAdd, TeamsEdit} from 'tower/controllers/Teams'; +import {PermissionsAdd, PermissionsList, PermissionsEdit} from 'tower/controllers/Permissions'; + + + +var tower = angular.module('Tower', [ 'ngRoute', 'ngSanitize', 'ngCookies', 'RestServices', - 'DataServices', - 'DashboardGraphs', + dataServices.name, + dashboardGraphs.name, 'AuthService', 'Utilities', - 'LicenseHelper', + Helpers.License.name, 'OrganizationFormDefinition', 'UserFormDefinition', 'FormGenerator', @@ -138,267 +166,267 @@ angular.module('Tower', [ when('/jobs', { templateUrl: urlPrefix + 'partials/jobs.html', - controller: 'JobsListController' + controller: JobsListController }). when('/portal', { templateUrl: urlPrefix + 'partials/portal.html', - controller: 'PortalController' + controller: PortalController }). when('/jobs/:id', { templateUrl: urlPrefix + 'partials/job_detail.html', - controller: 'JobDetailController' + controller: JobDetailController }). when('/jobs/:id/stdout', { templateUrl: urlPrefix + 'partials/job_stdout.html', - controller: 'JobStdoutController' + controller: JobStdoutController }). when('/job_templates', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: 'JobTemplatesList' + controller: JobTemplatesList }). when('/job_templates/add', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: 'JobTemplatesAdd' + controller: JobTemplatesAdd }). when('/job_templates/:template_id', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: 'JobTemplatesEdit' + controller: JobTemplatesEdit }). when('/job_templates/:id/schedules', { templateUrl: urlPrefix + 'partials/schedule_detail.html', - controller: 'ScheduleEditController' + controller: ScheduleEditController }). when('/projects', { templateUrl: urlPrefix + 'partials/projects.html', - controller: 'ProjectsList' + controller: ProjectsList }). when('/projects/add', { templateUrl: urlPrefix + 'partials/projects.html', - controller: 'ProjectsAdd' + controller: ProjectsAdd }). when('/projects/:id', { templateUrl: urlPrefix + 'partials/projects.html', - controller: 'ProjectsEdit' + controller: ProjectsEdit }). when('/projects/:id/schedules', { templateUrl: urlPrefix + 'partials/schedule_detail.html', - controller: 'ScheduleEditController' + controller: ScheduleEditController }). when('/projects/:project_id/organizations', { templateUrl: urlPrefix + 'partials/projects.html', - controller: 'OrganizationsList' + controller: OrganizationsList }). when('/projects/:project_id/organizations/add', { templateUrl: urlPrefix + 'partials/projects.html', - controller: 'OrganizationsAdd' + controller: OrganizationsAdd }). when('/inventories', { templateUrl: urlPrefix + 'partials/inventories.html', - controller: 'InventoriesList' + controller: InventoriesList }). when('/inventories/add', { templateUrl: urlPrefix + 'partials/inventories.html', - controller: 'InventoriesAdd' + controller: InventoriesAdd }). when('/inventories/:inventory_id', { templateUrl: urlPrefix + 'partials/inventory-edit.html', - controller: 'InventoriesEdit' + controller: InventoriesEdit }). when('/organizations', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: 'OrganizationsList' + controller: OrganizationsList }). when('/organizations/add', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: 'OrganizationsAdd' + controller: OrganizationsAdd }). when('/organizations/:organization_id', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: 'OrganizationsEdit' + controller: OrganizationsEdit }). when('/organizations/:organization_id/admins', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: 'AdminsList' + controller: AdminsList }). when('/organizations/:organization_id/users', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'UsersList' + controller: UsersList }). when('/organizations/:organization_id/users/add', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'UsersAdd' + controller: UsersAdd }). when('/organizations/:organization_id/users/:user_id', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'UsersEdit' + controller: UsersEdit }). when('/teams', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'TeamsList' + controller: TeamsList }). when('/teams/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'TeamsAdd' + controller: TeamsAdd }). when('/teams/:team_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'TeamsEdit' + controller: TeamsEdit }). when('/teams/:team_id/permissions/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'PermissionsAdd' + controller: PermissionsAdd }). when('/teams/:team_id/permissions', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'PermissionsList' + controller: PermissionsList }). when('/teams/:team_id/permissions/:permission_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'PermissionsEdit' + controller: PermissionsEdit }). when('/teams/:team_id/users', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'UsersList' + controller: UsersList }). when('/teams/:team_id/users/:user_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'UsersEdit' + controller: UsersEdit }). when('/teams/:team_id/projects', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'ProjectsList' + controller: ProjectsList }). when('/teams/:team_id/projects/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'ProjectsAdd' + controller: ProjectsAdd }). when('/teams/:team_id/projects/:project_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'ProjectsEdit' + controller: ProjectsEdit }). when('/teams/:team_id/credentials', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'CredentialsList' + controller: CredentialsList }). when('/teams/:team_id/credentials/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'CredentialsAdd' + controller: CredentialsAdd }). when('/teams/:team_id/credentials/:credential_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'CredentialsEdit' + controller: CredentialsEdit }). when('/credentials', { templateUrl: urlPrefix + 'partials/credentials.html', - controller: 'CredentialsList' + controller: CredentialsList }). when('/credentials/add', { templateUrl: urlPrefix + 'partials/credentials.html', - controller: 'CredentialsAdd' + controller: CredentialsAdd }). when('/credentials/:credential_id', { templateUrl: urlPrefix + 'partials/credentials.html', - controller: 'CredentialsEdit' + controller: CredentialsEdit }). when('/users', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'UsersList' + controller: UsersList }). when('/users/add', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'UsersAdd' + controller: UsersAdd }). when('/users/:user_id', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'UsersEdit' + controller: UsersEdit }). when('/users/:user_id/credentials', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'CredentialsList' + controller: CredentialsList }). when('/users/:user_id/permissions/add', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'PermissionsAdd' + controller: PermissionsAdd }). when('/users/:user_id/permissions', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'PermissionsList' + controller: PermissionsList }). when('/users/:user_id/permissions/:permission_id', { templateUrl: urlPrefix + 'partials/users.html', - controller: 'PermissionsEdit' + controller: PermissionsEdit }). when('/users/:user_id/credentials/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'CredentialsAdd' + controller: CredentialsAdd }). when('/teams/:user_id/credentials/:credential_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: 'CredentialsEdit' + controller: CredentialsEdit }). when('/login', { templateUrl: urlPrefix + 'partials/blank.html', - controller: 'Authenticate' + controller: Authenticate }). when('/logout', { templateUrl: urlPrefix + 'partials/blank.html', - controller: 'Authenticate' + controller: Authenticate }). when('/home', { templateUrl: urlPrefix + 'partials/home.html', - controller: 'Home', + controller: Home, resolve: { graphData: ['$q', 'jobStatusGraphData', 'hostCountGraphData', function($q, jobStatusGraphData, hostCountGraphData) { return $q.all({ @@ -411,17 +439,17 @@ angular.module('Tower', [ when('/home/groups', { templateUrl: urlPrefix + 'partials/subhome.html', - controller: 'HomeGroups' + controller: HomeGroups }). when('/home/hosts', { templateUrl: urlPrefix + 'partials/subhome.html', - controller: 'HomeHosts' + controller: HomeHosts }). when('/sockets', { templateUrl: urlPrefix + 'partials/sockets.html', - controller: 'SocketsController' + controller: SocketsController }). otherwise({ @@ -756,3 +784,6 @@ angular.module('Tower', [ LoadConfig(); } ]); + +export default tower; + diff --git a/awx/ui/static/js/controllers/Admins.js b/awx/ui/static/js/controllers/Admins.js index 3cc8e7f6b5..fe30a42444 100644 --- a/awx/ui/static/js/controllers/Admins.js +++ b/awx/ui/static/js/controllers/Admins.js @@ -19,7 +19,7 @@ */ -function AdminsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, AdminList, GenerateList, LoadBreadCrumbs, +export function AdminsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, AdminList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, GetBasePath, SelectionInit) { var list = AdminList, diff --git a/awx/ui/static/js/controllers/Authentication.js b/awx/ui/static/js/controllers/Authentication.js index dcc6ed0d38..8460ff2df6 100644 --- a/awx/ui/static/js/controllers/Authentication.js +++ b/awx/ui/static/js/controllers/Authentication.js @@ -59,7 +59,7 @@ */ -function Authenticate($log, $cookieStore, $compile, $window, $rootScope, $location, Authorization, ToggleClass, Alert, Wait, +export function Authenticate($log, $cookieStore, $compile, $window, $rootScope, $location, Authorization, ToggleClass, Alert, Wait, Timer, Empty, ClearScope) { var setLoginFocus, lastPath, sessionExpired, loginAgain, diff --git a/awx/ui/static/js/controllers/Credentials.js b/awx/ui/static/js/controllers/Credentials.js index 4e3f923b67..aca4a0899d 100644 --- a/awx/ui/static/js/controllers/Credentials.js +++ b/awx/ui/static/js/controllers/Credentials.js @@ -13,7 +13,7 @@ */ -function CredentialsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, CredentialList, +export function CredentialsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, CredentialList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, GetChoices, Wait, Stream) { @@ -134,7 +134,7 @@ CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeP ]; -function CredentialsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, CredentialForm, GenerateForm, Rest, Alert, +export function CredentialsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GenerateList, SearchInit, PaginateInit, LookUpInit, UserList, TeamList, GetBasePath, GetChoices, Empty, KindChange, OwnerChange, LoginMethodChange, FormSave) { @@ -286,7 +286,7 @@ CredentialsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log ]; -function CredentialsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, CredentialForm, GenerateForm, Rest, Alert, +export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, Prompt, GetBasePath, GetChoices, KindChange, UserList, TeamList, LookUpInit, Empty, OwnerChange, LoginMethodChange, FormSave, Stream, Wait) { diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index 9ae0f70cb3..4b74d4cd3f 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -25,7 +25,8 @@ * Host count graph should only be loaded if the user is a super user * */ -function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, DashboardJobs, + +export function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, DashboardJobs, ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, $window, graphData){ ClearScope('home'); @@ -136,7 +137,7 @@ Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', * @description This controls the 'home/groups' page that is loaded from the dashboard * */ -function HomeGroups($log, $scope, $filter, $compile, $location, $routeParams, LogViewer, HomeGroupList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, +export function HomeGroups($log, $scope, $filter, $compile, $location, $routeParams, LogViewer, HomeGroupList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath, SearchInit, PaginateInit, FormatDate, GetHostsStatusMsg, GetSyncStatusMsg, ViewUpdateStatus, Stream, GroupsEdit, Wait, Alert, Rest, Empty, InventoryUpdate, Find, GroupsCancelUpdate, Store, Socket) { @@ -572,7 +573,8 @@ HomeGroups.$inject = ['$log', '$scope', '$filter', '$compile', '$location', '$ro * @description This loads the page for 'home/hosts' * */ -function HomeHosts($scope, $location, $routeParams, HomeHostList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, + +export function HomeHosts($scope, $location, $routeParams, HomeHostList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath, SearchInit, PaginateInit, FormatDate, SetStatus, ToggleHostEnabled, HostsEdit, Stream, Find, ShowJobSummary, ViewJob) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index dd1def1f7e..e0ff5c5e4e 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -14,7 +14,7 @@ */ -function InventoriesList($scope, $rootScope, $location, $log, $routeParams, $compile, $filter, Rest, Alert, InventoryList, GenerateList, +export function InventoriesList($scope, $rootScope, $location, $log, $routeParams, $compile, $filter, Rest, Alert, InventoryList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, Wait, Stream, EditInventoryProperties, Find, Empty, LogViewer) { @@ -368,7 +368,7 @@ InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeP ]; -function InventoriesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, +export function InventoriesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GenerateList, OrganizationList, SearchInit, PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON) { @@ -478,7 +478,7 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log -function InventoriesEdit ($log, $scope, $location, $routeParams, $compile, GenerateList, ClearScope, Empty, Wait, Rest, Alert, LoadBreadCrumbs, GetBasePath, ProcessErrors, +export function InventoriesEdit ($log, $scope, $location, $routeParams, $compile, GenerateList, ClearScope, Empty, Wait, Rest, Alert, LoadBreadCrumbs, GetBasePath, ProcessErrors, Breadcrumbs, InventoryGroups, InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate, ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, EditInventoryProperties, ToggleHostEnabled, Stream, ShowJobSummary, InventoryGroupsHelp, HelpDialog, ViewJob, WatchInventoryWindowResize, GetHostContainerRows, GetGroupContainerRows, GetGroupContainerHeight, diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 5600d99881..b8e0a9a194 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -11,7 +11,7 @@ */ -function JobDetailController ($location, $rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, +export function JobDetailController ($location, $rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, SelectPlay, SelectTask, Socket, GetElapsed, DrawGraph, LoadHostSummary, ReloadHostSummaryList, JobIsFinished, SetTaskStyles, DigestEvent, UpdateDOM, EventViewer, DeleteJob, PlaybookRun, HostEventsViewer, LoadPlays, LoadTasks, LoadHosts, HostsEdit, ParseVariableString) { diff --git a/awx/ui/static/js/controllers/JobEvents.js b/awx/ui/static/js/controllers/JobEvents.js index 219bbc98fd..f55d1480ae 100644 --- a/awx/ui/static/js/controllers/JobEvents.js +++ b/awx/ui/static/js/controllers/JobEvents.js @@ -14,7 +14,7 @@ */ -function JobEventsList($sce, $filter, $scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobEventList, GenerateList, +export function JobEventsList($sce, $filter, $scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobEventList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren, FormatDate, EventView, Refresh, Wait) { @@ -260,7 +260,7 @@ JobEventsList.$inject = ['$sce', '$filter', '$scope', '$rootScope', '$location', 'GetBasePath', 'LookUpInit', 'ToggleChildren', 'FormatDate', 'EventView', 'Refresh', 'Wait' ]; -function JobEventsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventsForm, GenerateForm, +export function JobEventsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventsForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, GetBasePath, FormatDate, EventView, Wait) { ClearScope(); diff --git a/awx/ui/static/js/controllers/JobHosts.js b/awx/ui/static/js/controllers/JobHosts.js index 4a4882e960..399c4dba4e 100644 --- a/awx/ui/static/js/controllers/JobHosts.js +++ b/awx/ui/static/js/controllers/JobHosts.js @@ -14,7 +14,7 @@ */ -function JobHostSummaryList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobHostList, GenerateList, +export function JobHostSummaryList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobHostList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, Refresh, JobStatusToolTip) { diff --git a/awx/ui/static/js/controllers/JobStdout.js b/awx/ui/static/js/controllers/JobStdout.js index 42432320c1..8035ec7326 100644 --- a/awx/ui/static/js/controllers/JobStdout.js +++ b/awx/ui/static/js/controllers/JobStdout.js @@ -11,7 +11,7 @@ */ -function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, ClearScope, GetBasePath, Wait, Rest, ProcessErrors, Socket) { +export function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, ClearScope, GetBasePath, Wait, Rest, ProcessErrors, Socket) { ClearScope(); diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 70f74e2013..6efc6a7e27 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -14,7 +14,7 @@ */ -function JobTemplatesList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobTemplateList, +export function JobTemplatesList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobTemplateList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, JobTemplateForm, CredentialList, LookUpInit, PlaybookRun, Wait, Stream, CreateDialog, $compile) { @@ -246,7 +246,7 @@ JobTemplatesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$route 'PlaybookRun', 'Wait', 'Stream', 'CreateDialog' , '$compile' ]; -function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, +export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait, Empty, ToJSON, CallbackHelpInit, SurveyControllerInit, Prompt) { @@ -551,7 +551,7 @@ JobTemplatesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$lo ]; -function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, GenerateForm, Rest, +export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, CredentialList, ProjectList, LookUpInit, GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, Wait, Stream, Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit, JobsControllerInit, JobsListUpdate, diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js index f74d418075..8fafb44583 100644 --- a/awx/ui/static/js/controllers/Jobs.js +++ b/awx/ui/static/js/controllers/Jobs.js @@ -14,7 +14,7 @@ */ -function JobsListController ($rootScope, $log, $scope, $compile, $routeParams, ClearScope, Breadcrumbs, LoadBreadCrumbs, LoadSchedulesScope, +export function JobsListController ($rootScope, $log, $scope, $compile, $routeParams, ClearScope, Breadcrumbs, LoadBreadCrumbs, LoadSchedulesScope, LoadJobsScope, RunningJobsList, CompletedJobsList, QueuedJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait, Socket) { ClearScope(); diff --git a/awx/ui/static/js/controllers/Organizations.js b/awx/ui/static/js/controllers/Organizations.js index f289f9cc23..1726671feb 100644 --- a/awx/ui/static/js/controllers/Organizations.js +++ b/awx/ui/static/js/controllers/Organizations.js @@ -14,7 +14,7 @@ */ -function OrganizationsList($routeParams, $scope, $rootScope, $location, $log, Rest, Alert, LoadBreadCrumbs, Prompt, +export function OrganizationsList($routeParams, $scope, $rootScope, $location, $log, Rest, Alert, LoadBreadCrumbs, Prompt, GenerateList, OrganizationList, SearchInit, PaginateInit, ClearScope, ProcessErrors, GetBasePath, SelectionInit, Wait, Stream) { ClearScope(); @@ -101,7 +101,7 @@ OrganizationsList.$inject = ['$routeParams', '$scope', '$rootScope', '$location' ]; -function OrganizationsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, OrganizationForm, +export function OrganizationsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, OrganizationForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, GetBasePath, ReturnToCaller, Wait) { @@ -152,7 +152,7 @@ OrganizationsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$l ]; -function OrganizationsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, OrganizationForm, GenerateForm, Rest, +export function OrganizationsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, OrganizationForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, Prompt, ClearScope, GetBasePath, Wait, Stream) { ClearScope(); diff --git a/awx/ui/static/js/controllers/Permissions.js b/awx/ui/static/js/controllers/Permissions.js index b65a914460..67a2bcf012 100644 --- a/awx/ui/static/js/controllers/Permissions.js +++ b/awx/ui/static/js/controllers/Permissions.js @@ -14,7 +14,7 @@ */ -function PermissionsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, PermissionList, +export function PermissionsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, PermissionList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, CheckAccess, Wait) { @@ -102,7 +102,7 @@ PermissionsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeP ]; -function PermissionsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, PermissionsForm, +export function PermissionsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, PermissionsForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, GetBasePath, ReturnToCaller, InventoryList, ProjectList, LookUpInit, CheckAccess, Wait, PermissionCategoryChange) { @@ -199,7 +199,7 @@ PermissionsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log ]; -function PermissionsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, PermissionsForm, +export function PermissionsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, PermissionsForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, Prompt, GetBasePath, InventoryList, ProjectList, LookUpInit, CheckAccess, Wait, PermissionCategoryChange) { diff --git a/awx/ui/static/js/controllers/Portal.js b/awx/ui/static/js/controllers/Portal.js index 1cb329577d..3bffa0ac54 100644 --- a/awx/ui/static/js/controllers/Portal.js +++ b/awx/ui/static/js/controllers/Portal.js @@ -24,7 +24,7 @@ * * */ -function PortalController($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, ClearScope, Stream, Rest, GetBasePath, ProcessErrors, +export function PortalController($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, PortalJobsWidget, GenerateList, PortalJobTemplateList, SearchInit, PaginateInit, PlaybookRun){ ClearScope('portal'); diff --git a/awx/ui/static/js/controllers/Projects.js b/awx/ui/static/js/controllers/Projects.js index 0cec630e76..fbd10bb261 100644 --- a/awx/ui/static/js/controllers/Projects.js +++ b/awx/ui/static/js/controllers/Projects.js @@ -14,7 +14,7 @@ */ -function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, ProjectList, GenerateList, LoadBreadCrumbs, +export function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, ProjectList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, ProjectUpdate, Refresh, Wait, Stream, GetChoices, Empty, Find, LogViewer, GetProjectIcon, GetProjectToolTip) { @@ -391,7 +391,7 @@ ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routePara ]; -function ProjectsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, ProjectsForm, GenerateForm, Rest, Alert, ProcessErrors, +export function ProjectsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, ProjectsForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, GetBasePath, ReturnToCaller, GetProjectPath, LookUpInit, OrganizationList, CredentialList, GetChoices, DebugForm, Wait) { @@ -537,7 +537,7 @@ ProjectsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', ]; -function ProjectsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, ProjectsForm, +export function ProjectsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, ProjectsForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, Prompt, ClearScope, GetBasePath, ReturnToCaller, GetProjectPath, Authorization, CredentialList, LookUpInit, GetChoices, Empty, DebugForm, Wait, Stream, SchedulesControllerInit, SchedulesListInit, SchedulesList, ProjectUpdate) { diff --git a/awx/ui/static/js/controllers/Schedules.js b/awx/ui/static/js/controllers/Schedules.js index 953124ada0..f8a8fc9622 100644 --- a/awx/ui/static/js/controllers/Schedules.js +++ b/awx/ui/static/js/controllers/Schedules.js @@ -14,7 +14,7 @@ */ -function ScheduleEditController($scope, $compile, $location, $routeParams, SchedulesList, Rest, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, +export function ScheduleEditController($scope, $compile, $location, $routeParams, SchedulesList, Rest, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath, Wait, Breadcrumbs, Find, LoadDialogPartial, LoadSchedulesScope, GetChoices, Stream) { ClearScope(); diff --git a/awx/ui/static/js/controllers/Sockets.js b/awx/ui/static/js/controllers/Sockets.js index b652d6bda5..4c38f6964c 100644 --- a/awx/ui/static/js/controllers/Sockets.js +++ b/awx/ui/static/js/controllers/Sockets.js @@ -13,7 +13,7 @@ */ -function SocketsController ($scope, $compile, ClearScope, Socket) { +export function SocketsController ($scope, $compile, ClearScope, Socket) { ClearScope(); diff --git a/awx/ui/static/js/controllers/Teams.js b/awx/ui/static/js/controllers/Teams.js index b4e9bcbd8d..1ee97e649b 100644 --- a/awx/ui/static/js/controllers/Teams.js +++ b/awx/ui/static/js/controllers/Teams.js @@ -14,7 +14,7 @@ */ -function TeamsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, TeamList, GenerateList, LoadBreadCrumbs, +export function TeamsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, TeamList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, SetTeamListeners, GetBasePath, SelectionInit, Wait, Stream) { @@ -116,7 +116,7 @@ TeamsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams' ]; -function TeamsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, TeamForm, GenerateForm, +export function TeamsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GenerateList, OrganizationList, SearchInit, PaginateInit, GetBasePath, LookUpInit, Wait) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior @@ -177,7 +177,7 @@ TeamsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$r ]; -function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors, +export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt, GetBasePath, CheckAccess, OrganizationList, Wait, Stream) { diff --git a/awx/ui/static/js/controllers/Users.js b/awx/ui/static/js/controllers/Users.js index 8a2e74b673..003b65283c 100644 --- a/awx/ui/static/js/controllers/Users.js +++ b/awx/ui/static/js/controllers/Users.js @@ -13,7 +13,7 @@ */ -function UsersList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, UserList, GenerateList, LoadBreadCrumbs, +export function UsersList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, UserList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, Wait, Stream) { ClearScope(); @@ -105,7 +105,7 @@ UsersList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams' ]; -function UsersAdd($scope, $rootScope, $compile, $location, $log, $routeParams, UserForm, GenerateForm, Rest, Alert, ProcessErrors, +export function UsersAdd($scope, $rootScope, $compile, $location, $log, $routeParams, UserForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath, LookUpInit, OrganizationList, ResetForm, Wait) { ClearScope(); @@ -208,7 +208,7 @@ UsersAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$r ]; -function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeParams, UserForm, GenerateForm, Rest, Alert, +export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeParams, UserForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, GetBasePath, Prompt, CheckAccess, ResetForm, Wait, Stream) { diff --git a/awx/ui/static/js/directives/_dashboard-graphs.js b/awx/ui/static/js/directives/_dashboard-graphs.js index ed86866e80..ccacb29f77 100644 --- a/awx/ui/static/js/directives/_dashboard-graphs.js +++ b/awx/ui/static/js/directives/_dashboard-graphs.js @@ -1 +1,12 @@ -angular.module('DashboardGraphs', []); +import JobStatusGraph from 'tower/directives/job-status-graph'; +import HostCountGraph from 'tower/directives/host-count-graph'; +import HostStatusGraph from 'tower/directives/host-status-graph'; +import AutoSizeModule from 'tower/directives/auto-size-module'; +import AdjustGraphSize from 'tower/services/adjust-graph-size'; + +export default angular.module('DashboardGraphs', []) + .directive('jobStatusGraph', JobStatusGraph) + .directive('hostCountGraph', HostCountGraph) + .directive('hostStatusGraph', HostStatusGraph) + .directive('autoSizeModule', AutoSizeModule) + .service('adjustGraphSize', AdjustGraphSize); diff --git a/awx/ui/static/js/directives/auto-size-module.js b/awx/ui/static/js/directives/auto-size-module.js index e1851de160..3c63914a94 100644 --- a/awx/ui/static/js/directives/auto-size-module.js +++ b/awx/ui/static/js/directives/auto-size-module.js @@ -1,5 +1,9 @@ -angular.module('DashboardGraphs') -.directive('autoSizeModule', ['$window', function($window) { +export default + [ '$window', + AutoSizeModule + ]; + +function AutoSizeModule($window) { // Adjusts the size of the module so that all modules // fit into a single a page; assumes there are 2 rows @@ -32,4 +36,4 @@ angular.module('DashboardGraphs') }; -}]); +} diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 81675e780f..a9ea758e04 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -1,5 +1,10 @@ -angular.module('DashboardGraphs'). - directive('hostCountGraph', ['GetBasePath', 'Rest', 'adjustGraphSize', '$window', function(getBasePath, Rest, adjustGraphSize, $window) { +export default +[ 'adjustGraphSize', + '$window', + HostCountGraph +]; + +function HostCountGraph(adjustGraphSize, $window) { return { restrict: 'E', @@ -42,11 +47,11 @@ angular.module('DashboardGraphs'). { "key" : "Hosts" , "color" : "#1778c3", "values": data.hosts - }, - { "key" : "License" , - "color" : "#171717", - "values": data.hosts - } + }, + { "key" : "License" , + "color" : "#171717", + "values": data.hosts + } ]; graphData.map(function(series) { @@ -116,4 +121,4 @@ angular.module('DashboardGraphs'). } } -}]); +} diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index 11b0dbf227..b94e392f0e 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -1,6 +1,10 @@ -angular.module('DashboardGraphs') - .directive('hostStatusGraph', ['$compile', '$window', - function ($compile, $window) { +export default + [ '$compile', + '$window', + HostStatusGraph + ]; + +function HostStatusGraph($compile, $window) { return { restrict: 'E', link: link, @@ -99,4 +103,4 @@ angular.module('DashboardGraphs') } } - }]); + } diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 3cf6696bf2..97010587d6 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,6 +1,15 @@ -angular.module('DashboardGraphs') - .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'adjustGraphSize', 'jobStatusGraphData', - function ($rootScope, $compile , $location, $window, Wait, adjustGraphSize) { +export default + [ '$rootScope', + '$compile', + '$location' , + '$window', + 'Wait', + 'adjustGraphSize', + 'jobStatusGraphData', + JobStatusGraph + ]; + +function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustGraphSize) { return { restrict: 'E', templateUrl: '/static/partials/job_status_graph.html', @@ -117,4 +126,4 @@ angular.module('DashboardGraphs') } } - }]); + } diff --git a/awx/ui/static/js/forms.js b/awx/ui/static/js/forms.js new file mode 100644 index 0000000000..f405f8edae --- /dev/null +++ b/awx/ui/static/js/forms.js @@ -0,0 +1,58 @@ +import ActivityDetail from "tower/forms/ActivityDetail"; +import Credentials from "tower/forms/Credentials"; +import CustomInventory from "tower/forms/CustomInventory"; +import EventsViewer from "tower/forms/EventsViewer"; +import Groups from "tower/forms/Groups"; +import HostGroups from "tower/forms/HostGroups"; +import Hosts from "tower/forms/Hosts"; +import Inventories from "tower/forms/Inventories"; +import InventoryStatus from "tower/forms/InventoryStatus"; +import JobEventData from "tower/forms/JobEventData"; +import JobSummary from "tower/forms/JobSummary"; +import JobTemplates from "tower/forms/JobTemplates"; +import JobVarsPrompt from "tower/forms/JobVarsPrompt"; +import Jobs from "tower/forms/Jobs"; +import LicenseForm from "tower/forms/LicenseForm"; +import LicenseUpdate from "tower/forms/LicenseUpdate"; +import LogViewerOptions from "tower/forms/LogViewerOptions"; +import LogViewerStatus from "tower/forms/LogViewerStatus"; +import Organizations from "tower/forms/Organizations"; +import Permissions from "tower/forms/Permissions"; +import ProjectStatus from "tower/forms/ProjectStatus"; +import Projects from "tower/forms/Projects"; +import Source from "tower/forms/Source"; +import SurveyMaker from "tower/forms/SurveyMaker"; +import SurveyQuestion from "tower/forms/SurveyQuestion"; +import Teams from "tower/forms/Teams"; +import Users from "tower/forms/Users"; + + +export + { ActivityDetail, + Credentials, + CustomInventory, + EventsViewer, + Groups, + HostGroups, + Hosts, + Inventories, + InventoryStatus, + JobEventData, + JobSummary, + JobTemplates, + JobVarsPrompt, + Jobs, + LicenseForm, + LicenseUpdate, + LogViewerOptions, + LogViewerStatus, + Organizations, + Permissions, + ProjectStatus, + Projects, + Source, + SurveyMaker, + SurveyQuestion, + Teams, + Users + } diff --git a/awx/ui/static/js/forms/ActivityDetail.js b/awx/ui/static/js/forms/ActivityDetail.js index 269b281221..140fd389d9 100644 --- a/awx/ui/static/js/forms/ActivityDetail.js +++ b/awx/ui/static/js/forms/ActivityDetail.js @@ -16,33 +16,35 @@ * @name forms.function:ActivityDetail * @description This form is for activity detail modal that can be shown on most pages. */ -angular.module('ActivityDetailDefinition', []) - .value('ActivityDetailForm', { - name: 'activity', - editTitle: 'Activity Detail', - well: false, - 'class': 'horizontal-narrow', - formFieldSize: 'col-lg-10', - formLabelSize: 'col-lg-2', +export default + angular.module('ActivityDetailDefinition', []) + .value('ActivityDetailForm', { - fields: { - user: { - label: "Initiated by", - type: 'text', - readonly: true - }, - operation: { - label: 'Action', - type: 'text', - readonly: true - }, - changes: { - label: 'Changes', - type: 'textarea', - ngHide: "!changes || changes =='' || changes == 'null'", - readonly: true + name: 'activity', + editTitle: 'Activity Detail', + well: false, + 'class': 'horizontal-narrow', + formFieldSize: 'col-lg-10', + formLabelSize: 'col-lg-2', + + fields: { + user: { + label: "Initiated by", + type: 'text', + readonly: true + }, + operation: { + label: 'Action', + type: 'text', + readonly: true + }, + changes: { + label: 'Changes', + type: 'textarea', + ngHide: "!changes || changes =='' || changes == 'null'", + readonly: true + } } - } - }); //Form + }); //Form diff --git a/awx/ui/static/js/forms/Credentials.js b/awx/ui/static/js/forms/Credentials.js index 9d8a729481..d92c7fea5c 100644 --- a/awx/ui/static/js/forms/Credentials.js +++ b/awx/ui/static/js/forms/Credentials.js @@ -10,438 +10,440 @@ * @name forms.function:Credentials * @description This form is for adding/editing a Credential */ -angular.module('CredentialFormDefinition', []) - .value('CredentialForm', { - addTitle: 'Create Credential', //Legend in add mode - editTitle: '{{ name }}', //Legend in edit mode - name: 'credential', - well: true, - forceListeners: true, +export default + angular.module('CredentialFormDefinition', []) + .value('CredentialForm', { - actions: { - stream: { - ngClick: "showActivity()", - awToolTip: "View Activity Stream", - mode: 'edit' - } - }, + addTitle: 'Create Credential', //Legend in add mode + editTitle: '{{ name }}', //Legend in edit mode + name: 'credential', + well: true, + forceListeners: true, - fields: { - name: { - label: 'Name', - type: 'text', - addRequired: true, - editRequired: true, - autocomplete: false - }, - description: { - label: 'Description', - type: 'text', - addRequired: false, - editRequired: false - }, - owner: { - label: "Does this credential belong to a team or user?", - type: 'radio_group', - ngChange: "ownerChange()", - options: [{ - label: 'User', - value: 'user', - selected: true - }, { - label: 'Team', - value: 'team' - }], - awPopOver: "

A credential must be associated with either a user or a team. Choosing a user allows only the selected user access " + - "to the credential. Choosing a team shares the credential with all team members.

", - dataTitle: 'Owner', - dataPlacement: 'right', - dataContainer: "body" - }, - user: { - label: 'User that owns this credential', - type: 'lookup', - sourceModel: 'user', - sourceField: 'username', - ngClick: 'lookUpUser()', - ngShow: "owner == 'user'", - awRequiredWhen: { - variable: "user_required", - init: "false" + actions: { + stream: { + ngClick: "showActivity()", + awToolTip: "View Activity Stream", + mode: 'edit' } }, - team: { - label: 'Team that owns this credential', - type: 'lookup', - sourceModel: 'team', - sourceField: 'name', - ngClick: 'lookUpTeam()', - ngShow: "owner == 'team'", - awRequiredWhen: { - variable: "team_required", - init: "false" + + fields: { + name: { + label: 'Name', + type: 'text', + addRequired: true, + editRequired: true, + autocomplete: false + }, + description: { + label: 'Description', + type: 'text', + addRequired: false, + editRequired: false + }, + owner: { + label: "Does this credential belong to a team or user?", + type: 'radio_group', + ngChange: "ownerChange()", + options: [{ + label: 'User', + value: 'user', + selected: true + }, { + label: 'Team', + value: 'team' + }], + awPopOver: "

A credential must be associated with either a user or a team. Choosing a user allows only the selected user access " + + "to the credential. Choosing a team shares the credential with all team members.

", + dataTitle: 'Owner', + dataPlacement: 'right', + dataContainer: "body" + }, + user: { + label: 'User that owns this credential', + type: 'lookup', + sourceModel: 'user', + sourceField: 'username', + ngClick: 'lookUpUser()', + ngShow: "owner == 'user'", + awRequiredWhen: { + variable: "user_required", + init: "false" + } + }, + team: { + label: 'Team that owns this credential', + type: 'lookup', + sourceModel: 'team', + sourceField: 'name', + ngClick: 'lookUpTeam()', + ngShow: "owner == 'team'", + awRequiredWhen: { + variable: "team_required", + init: "false" + } + }, + kind: { + label: 'Type', + excludeModal: true, + type: 'select', + ngOptions: 'kind.label for kind in credential_kind_options track by kind.value', // select as label for value in array 'kind.label for kind in credential_kind_options', + ngChange: 'kindChange()', + addRequired: true, + editRequired: true, + awPopOver:'
\n' + + '
Machine
\n' + + '
Authentication for remote machine access. This can include SSH keys, usernames, passwords, ' + + 'and sudo information. Machine credentials are used when submitting jobs to run playbooks against ' + + 'remote hosts.
' + + '
Source Control
\n' + + '
Used to check out and synchronize playbook repositories with a remote source control ' + + 'management system such as Git, Subversion (svn), or Mercurial (hg). These credentials are ' + + 'used on the Projects tab.
\n' + + '
Others (Cloud Providers)
\n' + + '
Access keys for authenticating to the specific ' + + 'cloud provider, usually used for inventory sync ' + + 'and deployment.
\n' + + '
\n', + dataTitle: 'Type', + dataPlacement: 'right', + dataContainer: "body" + // helpCollapse: [{ + // hdr: 'Select a Credential Type', + // content: '
\n' + + // '
Machine
\n' + + // '
Authentication for remote machine access. This can include SSH keys, usernames, passwords, ' + + // 'and sudo information. Machine credentials are used when submitting jobs to run playbooks against ' + + // 'remote hosts.
' + + // '
Source Control
\n' + + // '
Used to check out and synchronize playbook repositories with a remote source control ' + + // 'management system such as Git, Subversion (svn), or Mercurial (hg). These credentials are ' + + // 'used on the Projects tab.
\n' + + // '
Others (Cloud Providers)
\n' + + // '
Access keys for authenticating to the specific ' + + // 'cloud provider, usually used for inventory sync ' + + // 'and deployment.
\n' + + // '
\n' + // }] + }, + access_key: { + label: 'Access Key', + type: 'text', + ngShow: "kind.value == 'aws'", + awRequiredWhen: { + variable: "aws_required", + init: false + }, + autocomplete: false, + apiField: 'username' + }, + secret_key: { + label: 'Secret Key', + type: 'password', + ngShow: "kind.value == 'aws'", + awRequiredWhen: { + variable: "aws_required", + init: false + }, + autocomplete: false, + ask: false, + clear: false, + apiField: 'passwowrd' + }, + "host": { + labelBind: 'hostLabel', + type: 'text', + ngShow: "kind.value == 'vmware'", + autocomplete: false, + awRequiredWhen: { + variable: 'host_required', + init: false + } + }, + "username": { + labelBind: 'usernameLabel', + type: 'text', + ngShow: "kind.value && kind.value !== 'aws' && " + + "kind.value !== 'gce' && kind.value!=='azure'", + awRequiredWhen: { + variable: 'username_required', + init: false + }, + autocomplete: false + }, + "email_address": { + labelBind: 'usernameLabel', + type: 'email', + ngShow: "kind.value === 'gce'", + awRequiredWhen: { + variable: 'email_required', + init: false + }, + autocomplete: false, + awPopOver: '

The email address assigned to the Google Compute Engine service account.

', + dataTitle: 'Email', + dataPlacement: 'right', + dataContainer: "body" + }, + "subscription_id": { + labelBind: "usernameLabel", + type: 'text', + ngShow: "kind.value == 'azure'", + awRequiredWhen: { + variable: 'subscription_required', + init: false + }, + addRequired: false, + editRequired: false, + autocomplete: false, + awPopOver: '

Subscription ID is an Azure construct, which is mapped to a username.

', + dataTitle: 'Subscription ID', + dataPlacement: 'right', + dataContainer: "body" + + }, + "api_key": { + label: 'API Key', + type: 'password', + ngShow: "kind.value == 'rax'", + awRequiredWhen: { + variable: "rackspace_required", + init: false + }, + autocomplete: false, + ask: false, + clear: false, + // apiField: 'passwowrd' + }, + "password": { + label: 'Password', + type: 'password', + ngShow: "kind.value == 'scm' || kind.value == 'vmware'", + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('password_confirm')", + ask: false, + clear: false, + associated: 'password_confirm', + autocomplete: false, + awRequiredWhen: { + variable: "password_required", + init: false + } + }, + "password_confirm": { + label: 'Confirm Password', + type: 'password', + ngShow: "kind.value == 'scm' || kind.value == 'vmware'", + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'password', + autocomplete: false, + awRequiredWhen: { + variable: "password_required", + init: false + } + }, + "ssh_password": { + label: 'Password', // formally 'SSH Password' + type: 'password', + ngShow: "kind.value == 'ssh'", + ngChange: "clearPWConfirm('ssh_password_confirm')", + addRequired: false, + editRequired: false, + ask: true, + clear: true, + associated: 'ssh_password_confirm', + autocomplete: false + }, + "ssh_password_confirm": { + label: 'Confirm Password', // formally 'Confirm SSH password' + type: 'password', + ngShow: "kind.value == 'ssh'", + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'ssh_password', + autocomplete: false + }, + "ssh_key_data": { + labelBind: 'sshKeyDataLabel', + type: 'textarea', + ngShow: "kind.value == 'ssh' || kind.value == 'scm' || " + + "kind.value == 'gce' || kind.value == 'azure'", + awRequiredWhen: { + variable: 'key_required', + init: true + }, + hintText: "{{ key_hint }}", + addRequired: false, + editRequired: false, + awDropFile: true, + 'class': 'ssh-key-field', + rows: 10, + awPopOver: "SSH key description", + awPopOverWatch: "key_description", + dataTitle: 'Help', + dataPlacement: 'right', + dataContainer: "body" + }, + "ssh_key_unlock": { + label: 'Key Password', + type: 'password', + ngShow: "kind.value == 'ssh' || kind.value == 'scm'", + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('ssh_key_unlock_confirm')", + associated: 'ssh_key_unlock_confirm', + ask: true, + askShow: "kind.value == 'ssh'", // Only allow ask for machine credentials + clear: true + }, + "ssh_key_unlock_confirm": { + label: 'Confirm Key Password', + type: 'password', + ngShow: "kind.value == 'ssh' || kind.value == 'scm'", + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'ssh_key_unlock' + }, + "login_method": { + label: "Login Method", // FIXME: Confirm this label is ok? + type: 'radio_group', + ngShow: "kind.value == 'ssh'", + ngChange: "loginMethodChange()", + options: [{ + label: 'None', // FIXME: Maybe 'Default' or 'SSH only' instead? + value: '', + selected: true + }, { + label: 'Sudo', + value: 'sudo' + }, { + label: 'Su', + value: 'su' + }], + awPopOver: "

A credential may optionally provide a sudo username and password or su username and password to use when running a playbook.

", + dataPlacement: 'right', + dataContainer: "body" + }, + "sudo_username": { + label: 'Sudo Username', + type: 'text', + ngShow: "kind.value == 'ssh' && login_method == 'sudo'", + addRequired: false, + editRequired: false, + autocomplete: false + }, + "sudo_password": { + label: 'Sudo Password', + type: 'password', + ngShow: "kind.value == 'ssh' && login_method == 'sudo'", + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('sudo_password_confirm')", + ask: true, + clear: true, + associated: 'sudo_password_confirm', + autocomplete: false + }, + "sudo_password_confirm": { + label: 'Confirm Sudo Password', + type: 'password', + ngShow: "kind.value == 'ssh' && login_method == 'sudo'", + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'sudo_password', + autocomplete: false + }, + "su_username": { + label: 'Su Username', + type: 'text', + ngShow: "kind.value == 'ssh' && login_method == 'su'", + addRequired: false, + editRequired: false, + autocomplete: false + }, + "su_password": { + label: 'Su Password', + type: 'password', + ngShow: "kind.value == 'ssh' && login_method == 'su'", + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('su_password_confirm')", + ask: true, + clear: true, + associated: 'su_password_confirm', + autocomplete: false + }, + "su_password_confirm": { + label: 'Confirm Su Password', + type: 'password', + ngShow: "kind.value == 'ssh' && login_method == 'su'", + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'su_password', + autocomplete: false + }, + "project": { + label: "Project", + type: 'text', + ngShow: "kind.value == 'gce'", + awRequiredWhen: { + variable: 'project_required', + init: false + }, + awPopOver: "

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

", + dataTitle: 'Project ID', + dataPlacement: 'right', + dataContainer: "body", + addRequired: false, + editRequired: false, + autocomplete: false + }, + + "vault_password": { + label: "Vault Password", + type: 'password', + ngShow: "kind.value == 'ssh'", + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('vault_password_confirm')", + ask: true, + clear: true, + associated: 'vault_password_confirm', + autocomplete: false + }, + "vault_password_confirm": { + label: "Confirm Vault Password", + type: 'password', + ngShow: "kind.value == 'ssh'", + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'vault_password', + autocomplete: false } }, - kind: { - label: 'Type', - excludeModal: true, - type: 'select', - ngOptions: 'kind.label for kind in credential_kind_options track by kind.value', // select as label for value in array 'kind.label for kind in credential_kind_options', - ngChange: 'kindChange()', - addRequired: true, - editRequired: true, - awPopOver:'
\n' + - '
Machine
\n' + - '
Authentication for remote machine access. This can include SSH keys, usernames, passwords, ' + - 'and sudo information. Machine credentials are used when submitting jobs to run playbooks against ' + - 'remote hosts.
' + - '
Source Control
\n' + - '
Used to check out and synchronize playbook repositories with a remote source control ' + - 'management system such as Git, Subversion (svn), or Mercurial (hg). These credentials are ' + - 'used on the Projects tab.
\n' + - '
Others (Cloud Providers)
\n' + - '
Access keys for authenticating to the specific ' + - 'cloud provider, usually used for inventory sync ' + - 'and deployment.
\n' + - '
\n', - dataTitle: 'Type', - dataPlacement: 'right', - dataContainer: "body" - // helpCollapse: [{ - // hdr: 'Select a Credential Type', - // content: '
\n' + - // '
Machine
\n' + - // '
Authentication for remote machine access. This can include SSH keys, usernames, passwords, ' + - // 'and sudo information. Machine credentials are used when submitting jobs to run playbooks against ' + - // 'remote hosts.
' + - // '
Source Control
\n' + - // '
Used to check out and synchronize playbook repositories with a remote source control ' + - // 'management system such as Git, Subversion (svn), or Mercurial (hg). These credentials are ' + - // 'used on the Projects tab.
\n' + - // '
Others (Cloud Providers)
\n' + - // '
Access keys for authenticating to the specific ' + - // 'cloud provider, usually used for inventory sync ' + - // 'and deployment.
\n' + - // '
\n' - // }] - }, - access_key: { - label: 'Access Key', - type: 'text', - ngShow: "kind.value == 'aws'", - awRequiredWhen: { - variable: "aws_required", - init: false + + buttons: { + save: { + label: 'Save', + ngClick: 'formSave()', //$scope.function to call on click, optional + ngDisabled: true //Disable when $pristine or $invalid, optional }, - autocomplete: false, - apiField: 'username' - }, - secret_key: { - label: 'Secret Key', - type: 'password', - ngShow: "kind.value == 'aws'", - awRequiredWhen: { - variable: "aws_required", - init: false - }, - autocomplete: false, - ask: false, - clear: false, - apiField: 'passwowrd' - }, - "host": { - labelBind: 'hostLabel', - type: 'text', - ngShow: "kind.value == 'vmware'", - autocomplete: false, - awRequiredWhen: { - variable: 'host_required', - init: false + reset: { + ngClick: 'formReset()', + ngDisabled: true //Disabled when $pristine } }, - "username": { - labelBind: 'usernameLabel', - type: 'text', - ngShow: "kind.value && kind.value !== 'aws' && " + - "kind.value !== 'gce' && kind.value!=='azure'", - awRequiredWhen: { - variable: 'username_required', - init: false - }, - autocomplete: false - }, - "email_address": { - labelBind: 'usernameLabel', - type: 'email', - ngShow: "kind.value === 'gce'", - awRequiredWhen: { - variable: 'email_required', - init: false - }, - autocomplete: false, - awPopOver: '

The email address assigned to the Google Compute Engine service account.

', - dataTitle: 'Email', - dataPlacement: 'right', - dataContainer: "body" - }, - "subscription_id": { - labelBind: "usernameLabel", - type: 'text', - ngShow: "kind.value == 'azure'", - awRequiredWhen: { - variable: 'subscription_required', - init: false - }, - addRequired: false, - editRequired: false, - autocomplete: false, - awPopOver: '

Subscription ID is an Azure construct, which is mapped to a username.

', - dataTitle: 'Subscription ID', - dataPlacement: 'right', - dataContainer: "body" - }, - "api_key": { - label: 'API Key', - type: 'password', - ngShow: "kind.value == 'rax'", - awRequiredWhen: { - variable: "rackspace_required", - init: false - }, - autocomplete: false, - ask: false, - clear: false, - // apiField: 'passwowrd' - }, - "password": { - label: 'Password', - type: 'password', - ngShow: "kind.value == 'scm' || kind.value == 'vmware'", - addRequired: false, - editRequired: false, - ngChange: "clearPWConfirm('password_confirm')", - ask: false, - clear: false, - associated: 'password_confirm', - autocomplete: false, - awRequiredWhen: { - variable: "password_required", - init: false - } - }, - "password_confirm": { - label: 'Confirm Password', - type: 'password', - ngShow: "kind.value == 'scm' || kind.value == 'vmware'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'password', - autocomplete: false, - awRequiredWhen: { - variable: "password_required", - init: false - } - }, - "ssh_password": { - label: 'Password', // formally 'SSH Password' - type: 'password', - ngShow: "kind.value == 'ssh'", - ngChange: "clearPWConfirm('ssh_password_confirm')", - addRequired: false, - editRequired: false, - ask: true, - clear: true, - associated: 'ssh_password_confirm', - autocomplete: false - }, - "ssh_password_confirm": { - label: 'Confirm Password', // formally 'Confirm SSH password' - type: 'password', - ngShow: "kind.value == 'ssh'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'ssh_password', - autocomplete: false - }, - "ssh_key_data": { - labelBind: 'sshKeyDataLabel', - type: 'textarea', - ngShow: "kind.value == 'ssh' || kind.value == 'scm' || " + - "kind.value == 'gce' || kind.value == 'azure'", - awRequiredWhen: { - variable: 'key_required', - init: true - }, - hintText: "{{ key_hint }}", - addRequired: false, - editRequired: false, - awDropFile: true, - 'class': 'ssh-key-field', - rows: 10, - awPopOver: "SSH key description", - awPopOverWatch: "key_description", - dataTitle: 'Help', - dataPlacement: 'right', - dataContainer: "body" - }, - "ssh_key_unlock": { - label: 'Key Password', - type: 'password', - ngShow: "kind.value == 'ssh' || kind.value == 'scm'", - addRequired: false, - editRequired: false, - ngChange: "clearPWConfirm('ssh_key_unlock_confirm')", - associated: 'ssh_key_unlock_confirm', - ask: true, - askShow: "kind.value == 'ssh'", // Only allow ask for machine credentials - clear: true - }, - "ssh_key_unlock_confirm": { - label: 'Confirm Key Password', - type: 'password', - ngShow: "kind.value == 'ssh' || kind.value == 'scm'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'ssh_key_unlock' - }, - "login_method": { - label: "Login Method", // FIXME: Confirm this label is ok? - type: 'radio_group', - ngShow: "kind.value == 'ssh'", - ngChange: "loginMethodChange()", - options: [{ - label: 'None', // FIXME: Maybe 'Default' or 'SSH only' instead? - value: '', - selected: true - }, { - label: 'Sudo', - value: 'sudo' - }, { - label: 'Su', - value: 'su' - }], - awPopOver: "

A credential may optionally provide a sudo username and password or su username and password to use when running a playbook.

", - dataPlacement: 'right', - dataContainer: "body" - }, - "sudo_username": { - label: 'Sudo Username', - type: 'text', - ngShow: "kind.value == 'ssh' && login_method == 'sudo'", - addRequired: false, - editRequired: false, - autocomplete: false - }, - "sudo_password": { - label: 'Sudo Password', - type: 'password', - ngShow: "kind.value == 'ssh' && login_method == 'sudo'", - addRequired: false, - editRequired: false, - ngChange: "clearPWConfirm('sudo_password_confirm')", - ask: true, - clear: true, - associated: 'sudo_password_confirm', - autocomplete: false - }, - "sudo_password_confirm": { - label: 'Confirm Sudo Password', - type: 'password', - ngShow: "kind.value == 'ssh' && login_method == 'sudo'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'sudo_password', - autocomplete: false - }, - "su_username": { - label: 'Su Username', - type: 'text', - ngShow: "kind.value == 'ssh' && login_method == 'su'", - addRequired: false, - editRequired: false, - autocomplete: false - }, - "su_password": { - label: 'Su Password', - type: 'password', - ngShow: "kind.value == 'ssh' && login_method == 'su'", - addRequired: false, - editRequired: false, - ngChange: "clearPWConfirm('su_password_confirm')", - ask: true, - clear: true, - associated: 'su_password_confirm', - autocomplete: false - }, - "su_password_confirm": { - label: 'Confirm Su Password', - type: 'password', - ngShow: "kind.value == 'ssh' && login_method == 'su'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'su_password', - autocomplete: false - }, - "project": { - label: "Project", - type: 'text', - ngShow: "kind.value == 'gce'", - awRequiredWhen: { - variable: 'project_required', - init: false - }, - awPopOver: "

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

", - dataTitle: 'Project ID', - dataPlacement: 'right', - dataContainer: "body", - addRequired: false, - editRequired: false, - autocomplete: false - }, + related: {} - "vault_password": { - label: "Vault Password", - type: 'password', - ngShow: "kind.value == 'ssh'", - addRequired: false, - editRequired: false, - ngChange: "clearPWConfirm('vault_password_confirm')", - ask: true, - clear: true, - associated: 'vault_password_confirm', - autocomplete: false - }, - "vault_password_confirm": { - label: "Confirm Vault Password", - type: 'password', - ngShow: "kind.value == 'ssh'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'vault_password', - autocomplete: false - } - }, - - buttons: { - save: { - label: 'Save', - ngClick: 'formSave()', //$scope.function to call on click, optional - ngDisabled: true //Disable when $pristine or $invalid, optional - }, - reset: { - ngClick: 'formReset()', - ngDisabled: true //Disabled when $pristine - } - }, - - related: {} - - }); + }); diff --git a/awx/ui/static/js/forms/CustomInventory.js b/awx/ui/static/js/forms/CustomInventory.js index 9730936e24..45f2279781 100644 --- a/awx/ui/static/js/forms/CustomInventory.js +++ b/awx/ui/static/js/forms/CustomInventory.js @@ -11,68 +11,70 @@ * @name forms.function:Organizations * @description This form is for adding/editing an organization */ -angular.module('CustomInventoryFormDefinition', []) - .value('CustomInventoryForm', { - addTitle: 'Create Custom Inventory', //Title in add mode - editTitle: '{{ name }}', //Title in edit mode - name: 'custom_inventory', //entity or model name in singular form - well: false, - showActions: false, +export default + angular.module('CustomInventoryFormDefinition', []) + .value('CustomInventoryForm', { - fields: { - name: { - label: 'Name', - type: 'text', - addRequired: true, - editRequired: true, - capitalize: false - }, - description: { - label: 'Description', - type: 'text', - addRequired: false, - editRequired: false - }, - organization: { - label: 'Organization', - type: 'lookup', - awRequiredWhen: { - variable: "orgrequired", - init: true + addTitle: 'Create Custom Inventory', //Title in add mode + editTitle: '{{ name }}', //Title in edit mode + name: 'custom_inventory', //entity or model name in singular form + well: false, + showActions: false, + + fields: { + name: { + label: 'Name', + type: 'text', + addRequired: true, + editRequired: true, + capitalize: false + }, + description: { + label: 'Description', + type: 'text', + addRequired: false, + editRequired: false + }, + organization: { + label: 'Organization', + type: 'lookup', + awRequiredWhen: { + variable: "orgrequired", + init: true + }, + sourceModel: 'organization', + sourceField: 'name', + ngClick: 'lookUpOrganization()' + }, + script: { + label: 'Custom Script', + type: 'textarea', + hintText: "Drag and drop an inventory script on the field below", + addRequired: true, + editRequired: true, + awDropFile: true, + 'class': 'ssh-key-field', + rows: 10, + awPopOver: "

Drag and drop your custom inventory script file here or create one in the field to import your custom inventory. " + + "

Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python

", + dataTitle: 'Custom Script', + dataPlacement: 'right', + dataContainer: "body" }, - sourceModel: 'organization', - sourceField: 'name', - ngClick: 'lookUpOrganization()' }, - script: { - label: 'Custom Script', - type: 'textarea', - hintText: "Drag and drop an inventory script on the field below", - addRequired: true, - editRequired: true, - awDropFile: true, - 'class': 'ssh-key-field', - rows: 10, - awPopOver: "

Drag and drop your custom inventory script file here or create one in the field to import your custom inventory. " + - "

Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python

", - dataTitle: 'Custom Script', - dataPlacement: 'right', - dataContainer: "body" + + buttons: { //for now always generates '+ - // ''+ - // '' - // }, - survey_enabled: { - label: 'Enable Survey', - type: 'checkbox', - addRequired: false, - editRequird: false, - // trueValue: true, - // falseValue: false, - ngChange: "surveyEnabled()", - column: 2, - awPopOver: "

If checked, user will be prompted at job launch with a series of questions related to the job.

", - dataPlacement: 'right', - dataTitle: 'Enable Survey', - dataContainer: "body" - }, - create_survey: { - type: 'custom', - column: 2, - control: ''+ - ''+ - '' - // label: 'Create Survey', - // type: 'text', - // addRequired: false, - // editRequired: false, - // // readonly: true, - // // ngShow: "survey_enabled", - // column: 2, - // awPopOver: "survey_help", - // awPopOverWatch: "survey_help", - // dataPlacement: 'right', - // dataTitle: 'Provisioning Callback URL', - // dataContainer: "body" - }, - allow_callbacks: { - label: 'Allow Provisioning Callbacks', - type: 'checkbox', - addRequired: false, - editRequird: false, - trueValue: 'true', - falseValue: 'false', - ngChange: "toggleCallback('host_config_key')", - column: 2, - awPopOver: "

Enables creation of a provisioning callback URL. Using the URL a host can contact Tower and request a configuration update " + - "using this job template.

", - dataPlacement: 'right', - dataTitle: 'Allow Provisioning Callbacks', - dataContainer: "body" - }, - callback_url: { - label: 'Provisioning Callback URL', - type: 'text', - addRequired: false, - editRequired: false, - readonly: true, - ngShow: "allow_callbacks", - column: 2, - awPopOver: "callback_help", - awPopOverWatch: "callback_help", - dataPlacement: 'right', - dataTitle: 'Provisioning Callback URL', - dataContainer: "body" - }, - host_config_key: { - label: 'Host Config Key', - type: 'text', - ngShow: "allow_callbacks", - ngChange: "configKeyChange()", - genMD5: true, - column: 2, - awPopOver: "callback_help", - awPopOverWatch: "callback_help", - dataPlacement: 'right', - dataTitle: "Host Config Key", - dataContainer: "body" - } - }, - - buttons: { //for now always generates '+ + // ''+ + // '' + // }, + survey_enabled: { + label: 'Enable Survey', + type: 'checkbox', + addRequired: false, + editRequird: false, + // trueValue: true, + // falseValue: false, + ngChange: "surveyEnabled()", + column: 2, + awPopOver: "

If checked, user will be prompted at job launch with a series of questions related to the job.

", + dataPlacement: 'right', + dataTitle: 'Enable Survey', + dataContainer: "body" + }, + create_survey: { + type: 'custom', + column: 2, + control: ''+ + ''+ + '' + // label: 'Create Survey', + // type: 'text', + // addRequired: false, + // editRequired: false, + // // readonly: true, + // // ngShow: "survey_enabled", + // column: 2, + // awPopOver: "survey_help", + // awPopOverWatch: "survey_help", + // dataPlacement: 'right', + // dataTitle: 'Provisioning Callback URL', + // dataContainer: "body" + }, + allow_callbacks: { + label: 'Allow Provisioning Callbacks', + type: 'checkbox', + addRequired: false, + editRequird: false, + trueValue: 'true', + falseValue: 'false', + ngChange: "toggleCallback('host_config_key')", + column: 2, + awPopOver: "

Enables creation of a provisioning callback URL. Using the URL a host can contact Tower and request a configuration update " + + "using this job template.

", + dataPlacement: 'right', + dataTitle: 'Allow Provisioning Callbacks', + dataContainer: "body" + }, + callback_url: { + label: 'Provisioning Callback URL', + type: 'text', + addRequired: false, + editRequired: false, + readonly: true, + ngShow: "allow_callbacks", + column: 2, + awPopOver: "callback_help", + awPopOverWatch: "callback_help", + dataPlacement: 'right', + dataTitle: 'Provisioning Callback URL', + dataContainer: "body" + }, + host_config_key: { + label: 'Host Config Key', + type: 'text', + ngShow: "allow_callbacks", + ngChange: "configKeyChange()", + genMD5: true, + column: 2, + awPopOver: "callback_help", + awPopOverWatch: "callback_help", + dataPlacement: 'right', + dataTitle: "Host Config Key", + dataContainer: "body" + } + }, + + buttons: { //for now always generates '+ - '
'+ - ''+ - ''//' - // label: 'Survey Name', - // type: 'text', - // addRequired: true, - // editRequired: true, - // capitalize: false, - // // column: 1 + fields: { + survey_name: { + type: 'custom', + control: '
'+ + // '
+ //
'+ + // ' + //
'+ + // ''+ + // '
Please enter a survey name.
'+ + // '
'+ + // '
'+ + // '
'+ + // '
'+ + // ''+ + // '
'+ + // '
'+ + '
'+ + ''+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + '
'//' + // label: 'Survey Name', + // type: 'text', + // addRequired: true, + // editRequired: true, + // capitalize: false, + // // column: 1 + }, + }, - }, - - // buttons: { //for now always generates '); + elem = angular.element(document.getElementById('configure-schedules-form-container')); + $compile(elem)(scope); + + if (scope.removeScheduleReady) { + scope.removeScheduleReady(); + } + scope.removeScheduleReady = scope.$on('ScheduleReady', function() { + // Insert the scheduler widget into the hidden div + scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); + scheduler.inject('configure-schedules-form', false); + scheduler.injectDetail('configure-schedules-detail', false); + scheduler.clear(); + scope.formShowing = true; + scope.showRRuleDetail = false; + scope.schedulesTitle = (mode === 'edit') ? 'Edit Schedule' : 'Create Schedule'; + + + // display the scheduler widget + showForm = function() { + $('#configure-jobs').show('slide', { direction: 'left' }, 500); + $('#configure-jobs').hide(); + Wait('stop'); + $('#configure-schedules-overlay').width($('#configure-schedules-tab') + .width()).height($('#configure-schedules-tab').height()).show(); + container.width($('#configure-schedules-tab').width() - 18); + SetSchedulesInnerDialogSize(); + container.show('slide', { direction: 'right' }, 300); + // scope.schedulerPurgeDays = (!Empty(scope.days)) ? Number(scope.days) : 30; + target.show(); + if(mode==="add"){ + scope.$apply(function(){ + scope.schedulerPurgeDays = 30; + scope.schedulerName = name+' Schedule'; + }); + } + if (mode === 'edit') { + scope.$apply(function() { + scheduler.setRRule(schedule.rrule); + scheduler.setName(schedule.name); + scope.schedulerPurgeDays = (!Empty(scope.days)) ? Number(scope.days) : 30; + }); + } + }; + setTimeout(function() { showForm(); }, 1000); + }); + + restoreList = function() { + // $('#group-save-button').prop('disabled', false); + $('#configure-jobs').show('slide', { direction: 'right' }, 500); + // $('#configure-jobs').width($('#configure-jobs').width()).height($('#configure-jobs').height()).hide(); + // parent_scope.refreshSchedules(); + list.show('slide', { direction: 'right' }, 500); + $('#configure-schedules-overlay').width($('#configure-schedules-tab').width()).height($('#configure-schedules-tab').height()).hide(); + parent_scope.refreshSchedules(); + }; - scope.configureSchedule = function(id, name) { - Rest.setUrl(scheduleUrl+id+'/schedules/'); + scope.showScheduleDetail = function() { + if (scope.formShowing) { + if (scheduler.isValid()) { + detail.width($('#configure-schedules-form').width()).height($('#configure-schedules-form').height()); + target.hide(); + detail.show(); + scope.formShowing = false; + } + } + else { + detail.hide(); + target.show(); + scope.formShowing = true; + } + }; + + if (scope.removeScheduleSaved) { + scope.removeScheduleSaved(); + } + scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { + Wait('stop'); + $("#configure-save-button").remove(); + container.hide('slide', { direction: 'left' }, 500, restoreList); + scope.$destroy(); + }); + + scope.saveScheduleForm = function() { + var extra_vars; + if (scheduler.isValid()) { + scope.schedulerIsValid = true; + url = (mode==="edit") ? GetBasePath('schedules')+id+'/' : url; + + extra_vars = { + "days" : scope.scheduler_form.schedulerPurgeDays.$viewValue + }; + schedule.extra_data = JSON.stringify(extra_vars); + + SchedulePost({ + scope: scope, + url: url, + scheduler: scheduler, + callback: 'ScheduleSaved', + mode: mode, + schedule: schedule + }); + } + else { + scope.schedulerIsValid = false; + } + }; + + scope.deleteSystemSchedule = function(){ + var hdr = 'Delete Schedule', + action = function () { + Wait('start'); + Rest.setUrl(schedule.url); + Rest.destroy() + .success(function () { + $('#prompt-modal').modal('hide'); + Wait('stop'); + // scope.$emit(callback, id); + scope.cancelScheduleForm(); + }) + .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 " + scope.schedulerName + " schedule?
", + action: action, + backdrop: false + }); + // } + // } else { + // //a schedule doesn't exist + // $("#prompt_action_btn").text('OK'); + // $('#prompt_cancel_btn').hide(); + // var action2 = function(){ + // $('#prompt-modal').modal('hide'); + // $("#prompt_action_btn").text('Yes'); + // $('#prompt_cancel_btn').show(); + // }; + // Prompt({ + // hdr: "Delete", + // body: "
No schedule exists for that job.
", + // action: action2, + // backdrop: false + // }); + // } + // }) + // .error(function(data, status) { + // ProcessErrors(scope, data, status, null, { hdr: 'Error!', + // msg: 'Failed updating job ' + scope.job_template_id + ' with variables. PUT returned: ' + status }); + // }); + }; + + scope.cancelScheduleForm = function() { + container.hide('slide', { direction: 'right' }, 500, restoreList); + $("#configure-save-button").remove(); + scope.$destroy(); + }; + + if (mode === 'edit') { + // Get the existing record + Rest.setUrl(url); //GetBasePath('schedules')+id+'/'); Rest.get() .success(function(data) { - if(data.count>0){ - scope.days=data.results[0].extra_data.days; - ConfigureTowerSchedule({ - scope: scope, - mode: 'edit', - url: scheduleUrl+id+'/schedules/' - }); - } else { - ConfigureTowerSchedule({ - scope: scope, - mode: 'add', - url: scheduleUrl+id+'/schedules/', - name: name - }); + schedule = data.results[0]; + id = schedule.id; + 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.$emit('ScheduleReady'); }) - .error(function(data, status) { + .error(function(data,status){ ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed getting schedule information' }); + msg: 'Failed to get: ' + url + ' GET returned: ' + status }); }); - - }; - - parent_scope.refreshJobs = function(){ - scope.search(SchedulesList.iterator); - }; - - }; - }]) - - -.factory('ConfigureTowerSchedule', ['$compile','SchedulerInit', 'Rest', 'Wait', 'SetSchedulesInnerDialogSize', 'SchedulePost', 'ProcessErrors', 'GetBasePath', 'Empty', 'Prompt', -function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, SchedulePost, ProcessErrors, GetBasePath, Empty, Prompt) { - return function(params) { - var parent_scope = params.scope, - mode = params.mode, // 'add' or 'edit' - url = params.url, - scope = parent_scope.$new(), - id = params.id || undefined, - name = params.name || undefined, - schedule = {}, - scheduler, - target, - showForm, - list, - detail, - restoreList, - container, - elem; - - Wait('start'); - // $('#configure-jobs').hide(); - detail = $('#configure-schedules-detail').hide(); - list = $('#configure-schedules-list'); - target = $('#configure-schedules-form'); - container = $('#configure-schedules-form-container'); - $("#configure-jobs").show(); - $("#configure-schedules-form-container").hide(); - scope.mode = mode; - // Clean up any lingering stuff - container.hide(); - target.empty(); - $('.tooltip').each(function () { - $(this).remove(); - }); - $('.popover').each(function () { - $(this).remove(); - }); - - $("#configure-cancel-button").after(''); - elem = angular.element(document.getElementById('configure-schedules-form-container')); - $compile(elem)(scope); - - if (scope.removeScheduleReady) { - scope.removeScheduleReady(); - } - scope.removeScheduleReady = scope.$on('ScheduleReady', function() { - // Insert the scheduler widget into the hidden div - scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); - scheduler.inject('configure-schedules-form', false); - scheduler.injectDetail('configure-schedules-detail', false); - scheduler.clear(); - scope.formShowing = true; - scope.showRRuleDetail = false; - scope.schedulesTitle = (mode === 'edit') ? 'Edit Schedule' : 'Create Schedule'; - - - // display the scheduler widget - showForm = function() { - $('#configure-jobs').show('slide', { direction: 'left' }, 500); - $('#configure-jobs').hide(); - Wait('stop'); - $('#configure-schedules-overlay').width($('#configure-schedules-tab') - .width()).height($('#configure-schedules-tab').height()).show(); - container.width($('#configure-schedules-tab').width() - 18); - SetSchedulesInnerDialogSize(); - container.show('slide', { direction: 'right' }, 300); - // scope.schedulerPurgeDays = (!Empty(scope.days)) ? Number(scope.days) : 30; - target.show(); - if(mode==="add"){ - scope.$apply(function(){ - scope.schedulerPurgeDays = 30; - scope.schedulerName = name+' Schedule'; - }); - } - if (mode === 'edit') { - scope.$apply(function() { - scheduler.setRRule(schedule.rrule); - scheduler.setName(schedule.name); - scope.schedulerPurgeDays = (!Empty(scope.days)) ? Number(scope.days) : 30; - }); - } - }; - setTimeout(function() { showForm(); }, 1000); - }); - - restoreList = function() { - // $('#group-save-button').prop('disabled', false); - $('#configure-jobs').show('slide', { direction: 'right' }, 500); - // $('#configure-jobs').width($('#configure-jobs').width()).height($('#configure-jobs').height()).hide(); - // parent_scope.refreshSchedules(); - list.show('slide', { direction: 'right' }, 500); - $('#configure-schedules-overlay').width($('#configure-schedules-tab').width()).height($('#configure-schedules-tab').height()).hide(); - parent_scope.refreshSchedules(); - - }; - - scope.showScheduleDetail = function() { - if (scope.formShowing) { - if (scheduler.isValid()) { - detail.width($('#configure-schedules-form').width()).height($('#configure-schedules-form').height()); - target.hide(); - detail.show(); - scope.formShowing = false; - } } else { - detail.hide(); - target.show(); - scope.formShowing = true; + scope.$emit('ScheduleReady'); } }; - - if (scope.removeScheduleSaved) { - scope.removeScheduleSaved(); - } - scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { - Wait('stop'); - $("#configure-save-button").remove(); - container.hide('slide', { direction: 'left' }, 500, restoreList); - scope.$destroy(); - }); - - scope.saveScheduleForm = function() { - var extra_vars; - if (scheduler.isValid()) { - scope.schedulerIsValid = true; - url = (mode==="edit") ? GetBasePath('schedules')+id+'/' : url; - - extra_vars = { - "days" : scope.scheduler_form.schedulerPurgeDays.$viewValue - }; - schedule.extra_data = JSON.stringify(extra_vars); - - SchedulePost({ - scope: scope, - url: url, - scheduler: scheduler, - callback: 'ScheduleSaved', - mode: mode, - schedule: schedule - }); - } - else { - scope.schedulerIsValid = false; - } - }; - - scope.deleteSystemSchedule = function(){ - var hdr = 'Delete Schedule', - action = function () { - Wait('start'); - Rest.setUrl(schedule.url); - Rest.destroy() - .success(function () { - $('#prompt-modal').modal('hide'); - Wait('stop'); - // scope.$emit(callback, id); - scope.cancelScheduleForm(); - }) - .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 " + scope.schedulerName + " schedule?
", - action: action, - backdrop: false - }); - // } - // } else { - // //a schedule doesn't exist - // $("#prompt_action_btn").text('OK'); - // $('#prompt_cancel_btn').hide(); - // var action2 = function(){ - // $('#prompt-modal').modal('hide'); - // $("#prompt_action_btn").text('Yes'); - // $('#prompt_cancel_btn').show(); - // }; - // Prompt({ - // hdr: "Delete", - // body: "
No schedule exists for that job.
", - // action: action2, - // backdrop: false - // }); - // } - // }) - // .error(function(data, status) { - // ProcessErrors(scope, data, status, null, { hdr: 'Error!', - // msg: 'Failed updating job ' + scope.job_template_id + ' with variables. PUT returned: ' + status }); - // }); - }; - - scope.cancelScheduleForm = function() { - container.hide('slide', { direction: 'right' }, 500, restoreList); - $("#configure-save-button").remove(); - scope.$destroy(); - }; - - if (mode === 'edit') { - // Get the existing record - Rest.setUrl(url); //GetBasePath('schedules')+id+'/'); - Rest.get() - .success(function(data) { - schedule = data.results[0]; - id = schedule.id; - 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.$emit('ScheduleReady'); - }) - .error(function(data,status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); - }); - } - else { - scope.$emit('ScheduleReady'); - } - }; -}]); + }]); diff --git a/awx/ui/static/js/helpers/Credentials.js b/awx/ui/static/js/helpers/Credentials.js index 12370dbdce..12f54457d5 100644 --- a/awx/ui/static/js/helpers/Credentials.js +++ b/awx/ui/static/js/helpers/Credentials.js @@ -5,263 +5,287 @@ * * */ - /** +/** * @ngdoc function * @name helpers.function:Credentials * @description Functions shared amongst Credential related controllers -*/ - + */ +export default angular.module('CredentialsHelper', ['Utilities']) .factory('KindChange', ['Empty', - function (Empty) { - return function (params) { + function (Empty) { + return function (params) { - var scope = params.scope, - reset = params.reset, - collapse, id; + 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 = 'Username'; - scope.aws_required = false; - scope.email_required = false; - scope.rackspace_required = false; - scope.sshKeyDataLabel = 'SSH 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 = "Paste the contents of the SSH private key file."; - scope.key_hint= "drag and drop an SSH private key file on the field below"; - scope.host_required = false; - scope.password_required = false; - scope.hostLabel = ''; + $('.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 = 'Username'; + scope.aws_required = false; + scope.email_required = false; + scope.rackspace_required = false; + scope.sshKeyDataLabel = 'SSH 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 = "Paste the contents of the SSH private key file.
esc or click to close
"; + scope.key_hint= "drag and drop an SSH private key file on the field below"; + scope.host_required = false; + scope.password_required = false; + scope.hostLabel = ''; - 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 = 'Username'; //formally 'SSH Username' - break; - case 'scm': - scope.sshKeyDataLabel = 'SCM Private Key'; - break; - case 'gce': - scope.usernameLabel = 'Service Account Email Address'; - scope.sshKeyDataLabel = 'RSA Private Key'; - scope.email_required = true; - scope.key_required = true; - scope.project_required = true; - scope.key_description = 'Paste the contents of the PEM file associated with the service account email.'; - scope.key_hint= "drag and drop a private key file on the field below"; - break; - case 'azure': - scope.usernameLabel = "Subscription ID"; - scope.sshKeyDataLabel = 'Management Certificate'; - scope.subscription_required = true; - scope.key_required = true; - scope.key_description = "Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console."; - scope.key_hint= "drag and drop a management certificate file on the field below"; - break; - case 'vmware': - scope.username_required = true; - scope.host_required = true; - scope.password_required = true; - scope.hostLabel = "vCenter Host"; - break; - } - } + $('.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 = 'Username'; + scope.aws_required = false; + scope.email_required = false; + scope.rackspace_required = false; + scope.sshKeyDataLabel = 'SSH 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 = "Paste the contents of the SSH private key file."; + scope.key_hint= "drag and drop an SSH private key file on the field below"; + scope.host_required = false; + scope.password_required = false; + scope.hostLabel = ''; - // 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.sudo_username = null; - scope.sudo_password = null; - scope.sudo_password_confirm = null; - scope.su_username = null; - scope.su_password = null; - scope.su_password_confirm = null; - } + 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 = 'Username'; //formally 'SSH Username' + break; + case 'scm': + scope.sshKeyDataLabel = 'SCM Private Key'; + break; + case 'gce': + scope.usernameLabel = 'Service Account Email Address'; + scope.sshKeyDataLabel = 'RSA Private Key'; + scope.email_required = true; + scope.key_required = true; + scope.project_required = true; + scope.key_description = 'Paste the contents of the PEM file associated with the service account email.'; + scope.key_hint= "drag and drop a private key file on the field below"; + break; + case 'azure': + scope.usernameLabel = "Subscription ID"; + scope.sshKeyDataLabel = 'Management Certificate'; + scope.subscription_required = true; + scope.key_required = true; + scope.key_description = "Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console."; + scope.key_hint= "drag and drop a management certificate file on the field below"; + break; + case 'vmware': + scope.username_required = true; + scope.host_required = true; + scope.password_required = true; + scope.hostLabel = "vCenter Host"; + break; + } + } - // 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); - } - } + // 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.sudo_username = null; + scope.sudo_password = null; + scope.sudo_password_confirm = null; + scope.su_username = null; + scope.su_password = null; + scope.su_password_confirm = 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('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; - } - }; - } + 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('LoginMethodChange', [ function () { - return function (params) { - var scope = params.scope, - login_method = scope.login_method; - if (login_method !== 'sudo') { - scope.sudo_username = null; - scope.sudo_password = null; - } - if (login_method !== 'su') { - scope.su_username = null; - scope.su_password = null; - } - }; - } + return function (params) { + var scope = params.scope, + login_method = scope.login_method; + if (login_method !== 'sudo') { + scope.sudo_username = null; + scope.sudo_password = null; + } + if (login_method !== 'su') { + scope.su_username = null; + scope.su_password = null; + } + }; +} ]) .factory('FormSave', ['$location', 'Alert', 'Rest', 'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm', 'ReturnToCaller', 'Wait', - function ($location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait) { - return function (params) { - var scope = params.scope, - mode = params.mode, - form = CredentialForm, - data = {}, fld, url; + function ($location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait) { + 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 (scope[fld] === null) { - data[fld] = ""; - } else { - data[fld] = scope[fld]; - } - } - } + for (fld in form.fields) { + if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' && + fld !== 'ssh_password') { + if (scope[fld] === null) { + data[fld] = ""; + } else { + data[fld] = scope[fld]; + } + } + } - if (!Empty(scope.team)) { - data.team = scope.team; - data.user = ""; - } else { - data.user = scope.user; - data.team = ""; - } + if (!Empty(scope.team)) { + data.team = scope.team; + data.user = ""; + } else { + data.user = scope.user; + data.team = ""; + } - data.kind = scope.kind.value; + data.kind = scope.kind.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_id; - } + 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_id; + } - if (Empty(data.team) && Empty(data.user)) { - Alert('Missing User or Team', 'You must provide either a User or a Team. If this credential will only be accessed by a specific ' + - 'user, select a User. To allow a team of users to access this credential, select a Team.', 'alert-danger'); - } else { - Wait('start'); - if (mode === 'add') { - url = (!Empty(data.team)) ? GetBasePath('teams') + data.team + '/credentials/' : - GetBasePath('users') + data.user + '/credentials/'; - Rest.setUrl(url); - Rest.post(data) - .success(function () { - Wait('stop'); - var base = $location.path().replace(/^\//, '').split('/')[0]; - if (base === 'credentials') { - ReturnToCaller(); - } - else { - ReturnToCaller(1); - } - }) - .error(function (data, status) { - Wait('stop'); - ProcessErrors(scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to create new Credential. POST status: ' + status - }); - }); - } else { - url = GetBasePath('credentials') + scope.id + '/'; - Rest.setUrl(url); - Rest.put(data) - .success(function () { - Wait('stop'); - var base = $location.path().replace(/^\//, '').split('/')[0]; - if (base === 'credentials') { - ReturnToCaller(); - } - else { - ReturnToCaller(1); - } - }) - .error(function (data, status) { - Wait('stop'); - ProcessErrors(scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to update Credential. PUT status: ' + status - }); - }); - } - } - }; - } + if (Empty(data.team) && Empty(data.user)) { + Alert('Missing User or Team', 'You must provide either a User or a Team. If this credential will only be accessed by a specific ' + + 'user, select a User. To allow a team of users to access this credential, select a Team.', 'alert-danger'); + } else { + Wait('start'); + if (mode === 'add') { + url = (!Empty(data.team)) ? GetBasePath('teams') + data.team + '/credentials/' : + GetBasePath('users') + data.user + '/credentials/'; + Rest.setUrl(url); + Rest.post(data) + .success(function () { + Wait('stop'); + var base = $location.path().replace(/^\//, '').split('/')[0]; + if (base === 'credentials') { + ReturnToCaller(); + } + else { + ReturnToCaller(1); + } + }) + .error(function (data, status) { + Wait('stop'); + ProcessErrors(scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to create new Credential. POST status: ' + status + }); + }); + } else { + url = GetBasePath('credentials') + scope.id + '/'; + Rest.setUrl(url); + Rest.put(data) + .success(function () { + Wait('stop'); + var base = $location.path().replace(/^\//, '').split('/')[0]; + if (base === 'credentials') { + ReturnToCaller(); + } + else { + ReturnToCaller(1); + } + }) + .error(function (data, status) { + Wait('stop'); + ProcessErrors(scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to update Credential. PUT status: ' + status + }); + }); + } + } + }; + } ]); diff --git a/awx/ui/static/js/helpers/CustomInventory.js b/awx/ui/static/js/helpers/CustomInventory.js index 915be61d4a..a4f8ca16c5 100644 --- a/awx/ui/static/js/helpers/CustomInventory.js +++ b/awx/ui/static/js/helpers/CustomInventory.js @@ -12,346 +12,346 @@ */ +export default + angular.module('CreateCustomInventoryHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ModalDialog', + 'GeneratorHelpers', 'CustomInventoryFormDefinition']) -angular.module('CreateCustomInventoryHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ModalDialog', - 'GeneratorHelpers', 'CustomInventoryFormDefinition']) + .factory('CreateCustomInventory', ['Wait', 'CreateDialog', 'CustomInventoryList', 'GenerateList', 'GetBasePath' , 'SearchInit' , 'PaginateInit', 'PlaybookRun', 'CustomInventoryAdd', + 'SchedulesList', 'CustomInventoryEdit', 'Rest' , 'ProcessErrors', 'CustomInventoryForm', 'GenerateForm', 'Prompt', + function(Wait, CreateDialog, CustomInventoryList, GenerateList, GetBasePath, SearchInit, PaginateInit, PlaybookRun, CustomInventoryAdd, + SchedulesList, CustomInventoryEdit, Rest, ProcessErrors, CustomInventoryForm, GenerateForm, Prompt) { + return function(params) { + // Set modal dimensions based on viewport width - .factory('CreateCustomInventory', ['Wait', 'CreateDialog', 'CustomInventoryList', 'GenerateList', 'GetBasePath' , 'SearchInit' , 'PaginateInit', 'PlaybookRun', 'CustomInventoryAdd', - 'SchedulesList', 'CustomInventoryEdit', 'Rest' , 'ProcessErrors', 'CustomInventoryForm', 'GenerateForm', 'Prompt', - function(Wait, CreateDialog, CustomInventoryList, GenerateList, GetBasePath, SearchInit, PaginateInit, PlaybookRun, CustomInventoryAdd, - SchedulesList, CustomInventoryEdit, Rest, ProcessErrors, CustomInventoryForm, GenerateForm, Prompt) { - return function(params) { - // Set modal dimensions based on viewport width + var scope = params.parent_scope.$new(), + callback = 'OpenConfig', + defaultUrl = GetBasePath('inventory_scripts'), + list = CustomInventoryList, + view = GenerateList, + buttons = [ + { + "label": "Close", + "onClick": function() { + // $(this).dialog('close'); + scope.cancelConfigure(); + }, + "icon": "fa-times", + "class": "btn btn-default", + "id": "script-close-button" + } + ]; - var scope = params.parent_scope.$new(), - callback = 'OpenConfig', - defaultUrl = GetBasePath('inventory_scripts'), - list = CustomInventoryList, - view = GenerateList, - buttons = [ - { - "label": "Close", - "onClick": function() { - // $(this).dialog('close'); + scope.cleanupJob = true; + + if(scope.removeOpenConfig) { + scope.removeOpenConfig(); + } + scope.removeOpenConfig = scope.$on('OpenConfig', function() { + $('#custom-script-dialog').dialog('open'); + $('#script-close-button').focus(); + $('#script-close-button').blur(); + }); + + view.inject( list, { + id : 'custom-script-dialog', + mode: 'edit', + scope: scope, + breadCrumbs: false, + activityStream: false, + showSearch: true + }); + + SearchInit({ + scope: scope, + set: 'source_scripts' , // 'custom_inventories', + list: list, + url: defaultUrl + }); + PaginateInit({ + scope: scope, + list: list, + url: defaultUrl + }); + + scope.search(list.iterator); + + // SchedulesControllerInit({ + // scope: scope, + // parent_scope: parent_scope, + // // list: list + // }); + + + CreateDialog({ + id: 'custom-script-dialog', + title: 'Inventory Scripts', + target: 'custom-script-dialog', + scope: scope, + buttons: buttons, + width: 700, + height: 800, + minWidth: 400, + callback: callback, + onClose: function () { + // Destroy on close + $('.tooltip').each(function () { + // Remove any lingering tooltip
elements + $(this).remove(); + }); + $('.popover').each(function () { + // remove lingering popover
elements + $(this).remove(); + }); + // $("#configure-jobs").show(); + // $("#configure-schedules-form-container").hide(); + // $('#configure-schedules-list').empty(); + // $('#configure-schedules-form').empty(); + // $('#configure-schedules-detail').empty(); + // $('#configure-tower-dialog').hide(); + $(this).dialog('destroy'); scope.cancelConfigure(); }, - "icon": "fa-times", - "class": "btn btn-default", - "id": "script-close-button" - } - ]; - - scope.cleanupJob = true; - - if(scope.removeOpenConfig) { - scope.removeOpenConfig(); - } - scope.removeOpenConfig = scope.$on('OpenConfig', function() { - $('#custom-script-dialog').dialog('open'); - $('#script-close-button').focus(); - $('#script-close-button').blur(); - }); - - view.inject( list, { - id : 'custom-script-dialog', - mode: 'edit', - scope: scope, - breadCrumbs: false, - activityStream: false, - showSearch: true - }); - - SearchInit({ - scope: scope, - set: 'source_scripts' , // 'custom_inventories', - list: list, - url: defaultUrl - }); - PaginateInit({ - scope: scope, - list: list, - url: defaultUrl - }); - - scope.search(list.iterator); - - // SchedulesControllerInit({ - // scope: scope, - // parent_scope: parent_scope, - // // list: list - // }); - - - CreateDialog({ - id: 'custom-script-dialog', - title: 'Inventory Scripts', - target: 'custom-script-dialog', - scope: scope, - buttons: buttons, - width: 700, - height: 800, - minWidth: 400, - callback: callback, - onClose: function () { - // Destroy on close - $('.tooltip').each(function () { - // Remove any lingering tooltip
elements - $(this).remove(); - }); - $('.popover').each(function () { - // remove lingering popover
elements - $(this).remove(); - }); - // $("#configure-jobs").show(); - // $("#configure-schedules-form-container").hide(); - // $('#configure-schedules-list').empty(); - // $('#configure-schedules-form').empty(); - // $('#configure-schedules-detail').empty(); - // $('#configure-tower-dialog').hide(); - $(this).dialog('destroy'); - scope.cancelConfigure(); - }, - }); - - - - // Cancel - scope.cancelConfigure = function () { - try { - $('#custom-script-dialog').dialog('close'); - } - catch(e) { - //ignore - } - if (scope.searchCleanup) { - scope.searchCleanup(); - } - // if (!Empty(parent_scope) && parent_scope.restoreSearch) { - // parent_scope.restoreSearch(); - // } - else { - Wait('stop'); - } - }; - - scope.editCustomInv = function(id){ - CustomInventoryEdit({ - scope: scope, - id: id }); - }; - scope.deleteCustomInv = function(id, name){ - var action = function () { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .success(function () { - scope.search(list.iterator); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); + + + // Cancel + scope.cancelConfigure = function () { + try { + $('#custom-script-dialog').dialog('close'); + } + catch(e) { + //ignore + } + if (scope.searchCleanup) { + scope.searchCleanup(); + } + // if (!Empty(parent_scope) && parent_scope.restoreSearch) { + // parent_scope.restoreSearch(); + // } + else { + Wait('stop'); + } }; - Prompt({ - hdr: 'Delete', - body: "
Are you sure you want to delete " + name + "?
", - action: action + scope.editCustomInv = function(id){ + CustomInventoryEdit({ + scope: scope, + id: id + }); + }; + scope.deleteCustomInv = function(id, name){ + + var action = function () { + $('#prompt-modal').modal('hide'); + Wait('start'); + var url = defaultUrl + id + '/'; + Rest.setUrl(url); + Rest.destroy() + .success(function () { + scope.search(list.iterator); + }) + .error(function (data, status) { + 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 " + name + "?
", + action: action + }); + }; + + scope.addCustomInv = function(){ + CustomInventoryAdd({ + scope: scope + }); + }; + + + + }; + }]) + + + .factory('CustomInventoryAdd', ['$compile','SchedulerInit', 'Rest', 'Wait', 'CustomInventoryList', 'CustomInventoryForm', 'ProcessErrors', 'GetBasePath', 'Empty', 'GenerateForm', + 'SearchInit' , 'PaginateInit', 'GenerateList', 'LookUpInit', 'OrganizationList', + function($compile, SchedulerInit, Rest, Wait, CustomInventoryList, CustomInventoryForm, ProcessErrors, GetBasePath, Empty, GenerateForm, + SearchInit, PaginateInit, GenerateList, LookUpInit, OrganizationList) { + return function(params) { + var scope = params.scope, + generator = GenerateForm, + form = CustomInventoryForm, + view = GenerateList, + list = CustomInventoryList, + url = GetBasePath('inventory_scripts'); + + generator.inject(form, { id:'custom-script-dialog', mode: 'add' , scope:scope, related: false, breadCrumbs: false}); + generator.reset(); + + LookUpInit({ + url: GetBasePath('organization'), + scope: scope, + form: form, + // hdr: "Select Custom Inventory", + list: OrganizationList, + field: 'organization', + input_type: 'radio' }); + + // Save + scope.formSave = function () { + generator.clearApiErrors(); + Wait('start'); + Rest.setUrl(url); + Rest.post({ name: scope.name, description: scope.description, organization: scope.organization, script: scope.script }) + .success(function () { + view.inject( list, { + id : 'custom-script-dialog', + mode: 'edit', + scope: scope, + breadCrumbs: false, + activityStream: false, + showSearch: true + }); + + SearchInit({ + scope: scope, + set: 'source_scripts', //'custom_inventories', + list: list, + url: url + }); + PaginateInit({ + scope: scope, + list: list, + url: url + }); + + scope.search(list.iterator); + Wait('stop'); + Wait('stop'); + + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to add new inventory script. POST returned status: ' + status }); + }); }; - scope.addCustomInv = function(){ - CustomInventoryAdd({ - scope: scope - }); + // Cancel + scope.formReset = function () { + generator.reset(); }; - - - }; }]) + .factory('CustomInventoryEdit', ['$compile','CustomInventoryList', 'Rest', 'Wait', 'GenerateList', 'CustomInventoryForm', 'ProcessErrors', 'GetBasePath', 'Empty', 'GenerateForm', + 'SearchInit', 'PaginateInit', '$routeParams', 'OrganizationList', 'LookUpInit', + function($compile, CustomInventoryList, Rest, Wait, GenerateList, CustomInventoryForm, ProcessErrors, GetBasePath, Empty, GenerateForm, + SearchInit, PaginateInit, $routeParams, OrganizationList, LookUpInit) { + return function(params) { + var scope = params.scope, + id = params.id, + generator = GenerateForm, + form = CustomInventoryForm, + view = GenerateList, + list = CustomInventoryList, + master = {}, + url = GetBasePath('inventory_scripts'); -.factory('CustomInventoryAdd', ['$compile','SchedulerInit', 'Rest', 'Wait', 'CustomInventoryList', 'CustomInventoryForm', 'ProcessErrors', 'GetBasePath', 'Empty', 'GenerateForm', - 'SearchInit' , 'PaginateInit', 'GenerateList', 'LookUpInit', 'OrganizationList', -function($compile, SchedulerInit, Rest, Wait, CustomInventoryList, CustomInventoryForm, ProcessErrors, GetBasePath, Empty, GenerateForm, - SearchInit, PaginateInit, GenerateList, LookUpInit, OrganizationList) { - return function(params) { - var scope = params.scope, - generator = GenerateForm, - form = CustomInventoryForm, - view = GenerateList, - list = CustomInventoryList, - url = GetBasePath('inventory_scripts'); + generator.inject(form, { + id:'custom-script-dialog', + mode: 'edit' , + scope:scope, + related: false, + breadCrumbs: false, + activityStream: false + }); + generator.reset(); + LookUpInit({ + url: GetBasePath('organization'), + scope: scope, + form: form, + // hdr: "Select Custom Inventory", + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); - generator.inject(form, { id:'custom-script-dialog', mode: 'add' , scope:scope, related: false, breadCrumbs: false}); - generator.reset(); - - LookUpInit({ - url: GetBasePath('organization'), - scope: scope, - form: form, - // hdr: "Select Custom Inventory", - list: OrganizationList, - field: 'organization', - input_type: 'radio' - }); - - // Save - scope.formSave = function () { - generator.clearApiErrors(); + // Retrieve detail record and prepopulate the form Wait('start'); - Rest.setUrl(url); - Rest.post({ name: scope.name, description: scope.description, organization: scope.organization, script: scope.script }) - .success(function () { - view.inject( list, { - id : 'custom-script-dialog', - mode: 'edit', - scope: scope, - breadCrumbs: false, - activityStream: false, - showSearch: true - }); + Rest.setUrl(url + id+'/'); + Rest.get() + .success(function (data) { + var fld; + for (fld in form.fields) { + if (data[fld]) { + scope[fld] = data[fld]; + master[fld] = data[fld]; + } - SearchInit({ - scope: scope, - set: 'source_scripts', //'custom_inventories', - list: list, - url: url - }); - PaginateInit({ - scope: scope, - list: list, - url: url - }); - - scope.search(list.iterator); + 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]; + } + } Wait('stop'); - Wait('stop'); - }) .error(function (data, status) { ProcessErrors(scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to add new inventory script. POST returned status: ' + status }); + msg: 'Failed to retrieve inventory script: ' + $routeParams.id + '. GET status: ' + status }); }); - }; - // Cancel - scope.formReset = function () { - generator.reset(); - }; - }; -}]) + scope.formSave = function () { + generator.clearApiErrors(); + Wait('start'); + Rest.setUrl(url+ id+'/'); + Rest.put({ name: scope.name, description: scope.description, organization: scope.organization, script: scope.script }) + .success(function () { + view.inject( list, { + id : 'custom-script-dialog', + mode: 'edit', + scope: scope, + breadCrumbs: false, + activityStream: false, + showSearch: true + }); -.factory('CustomInventoryEdit', ['$compile','CustomInventoryList', 'Rest', 'Wait', 'GenerateList', 'CustomInventoryForm', 'ProcessErrors', 'GetBasePath', 'Empty', 'GenerateForm', - 'SearchInit', 'PaginateInit', '$routeParams', 'OrganizationList', 'LookUpInit', -function($compile, CustomInventoryList, Rest, Wait, GenerateList, CustomInventoryForm, ProcessErrors, GetBasePath, Empty, GenerateForm, - SearchInit, PaginateInit, $routeParams, OrganizationList, LookUpInit) { - return function(params) { - var scope = params.scope, - id = params.id, - generator = GenerateForm, - form = CustomInventoryForm, - view = GenerateList, - list = CustomInventoryList, - master = {}, - url = GetBasePath('inventory_scripts'); + SearchInit({ + scope: scope, + set: 'source_scripts', //'custom_inventories', + list: list, + url: url + }); + PaginateInit({ + scope: scope, + list: list, + url: url + }); - generator.inject(form, { - id:'custom-script-dialog', - mode: 'edit' , - scope:scope, - related: false, - breadCrumbs: false, - activityStream: false - }); - generator.reset(); - LookUpInit({ - url: GetBasePath('organization'), - scope: scope, - form: form, - // hdr: "Select Custom Inventory", - list: OrganizationList, - field: 'organization', - input_type: 'radio' - }); + scope.search(list.iterator); - // Retrieve detail record and prepopulate the form - Wait('start'); - Rest.setUrl(url + id+'/'); - Rest.get() - .success(function (data) { - var fld; - for (fld in form.fields) { - if (data[fld]) { - scope[fld] = data[fld]; - master[fld] = data[fld]; - } + Wait('stop'); - 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]; - } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to add new inventory script. PUT returned status: ' + status }); + }); + }; + + scope.formReset = function () { + generator.reset(); + for (var fld in master) { + scope[fld] = master[fld]; } - Wait('stop'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to retrieve inventory script: ' + $routeParams.id + '. GET status: ' + status }); - }); + scope.organization_name = master.organization_name; - scope.formSave = function () { - generator.clearApiErrors(); - Wait('start'); - Rest.setUrl(url+ id+'/'); - Rest.put({ name: scope.name, description: scope.description, organization: scope.organization, script: scope.script }) - .success(function () { - view.inject( list, { - id : 'custom-script-dialog', - mode: 'edit', - scope: scope, - breadCrumbs: false, - activityStream: false, - showSearch: true - }); - - SearchInit({ - scope: scope, - set: 'source_scripts', //'custom_inventories', - list: list, - url: url - }); - PaginateInit({ - scope: scope, - list: list, - url: url - }); - - scope.search(list.iterator); - - Wait('stop'); - - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to add new inventory script. PUT returned status: ' + status }); - }); + }; }; - - scope.formReset = function () { - generator.reset(); - for (var fld in master) { - scope[fld] = master[fld]; - } - scope.organization_name = master.organization_name; - - }; - }; -}]); + }]); diff --git a/awx/ui/static/js/helpers/EventViewer.js b/awx/ui/static/js/helpers/EventViewer.js index 240d990737..945275ea1c 100644 --- a/awx/ui/static/js/helpers/EventViewer.js +++ b/awx/ui/static/js/helpers/EventViewer.js @@ -10,555 +10,555 @@ * @description eventviewerhelper */ +export default + angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFormDefinition', 'HostsHelper']) -angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFormDefinition', 'HostsHelper']) + .factory('EventViewer', ['$compile', 'CreateDialog', 'GetEvent', 'Wait', 'EventAddTable', 'GetBasePath', 'LookUpName', 'Empty', 'EventAddPreFormattedText', + function($compile, CreateDialog, GetEvent, Wait, EventAddTable, GetBasePath, LookUpName, Empty, EventAddPreFormattedText) { + return function(params) { + var parent_scope = params.scope, + url = params.url, + event_id = params.event_id, + parent_id = params.parent_id, + title = params.title, //optional + scope = parent_scope.$new(true), + index = params.index, + page, + current_event; - .factory('EventViewer', ['$compile', 'CreateDialog', 'GetEvent', 'Wait', 'EventAddTable', 'GetBasePath', 'LookUpName', 'Empty', 'EventAddPreFormattedText', - function($compile, CreateDialog, GetEvent, Wait, EventAddTable, GetBasePath, LookUpName, Empty, EventAddPreFormattedText) { - return function(params) { - var parent_scope = params.scope, - url = params.url, - event_id = params.event_id, - parent_id = params.parent_id, - title = params.title, //optional - scope = parent_scope.$new(true), - index = params.index, - page, - current_event; - - if (scope.removeShowNextEvent) { - scope.removeShowNextEvent(); - } - scope.removeShowNextEvent = scope.$on('ShowNextEvent', function(e, data, show_event) { - scope.events = data; - $('#event-next-spinner').hide(400); - if (show_event === 'prev') { - showEvent(scope.events.length - 1); + if (scope.removeShowNextEvent) { + scope.removeShowNextEvent(); } - else if (show_event === 'next') { - showEvent(0); - } - }); - - // show scope.events[idx] - function showEvent(idx) { - var show_tabs = false, elem, data; - - if (idx > scope.events.length - 1) { - GetEvent({ - scope: scope, - url: scope.next_event_set, - show_event: 'next' - }); - return; - } - - if (idx < 0) { - GetEvent({ - scope: scope, - url: scope.prev_event_set, - show_event: 'prev' - }); - return; - } - - data = scope.events[idx]; - current_event = idx; - - $('#status-form-container').empty(); - $('#results-form-container').empty(); - $('#timing-form-container').empty(); - $('#stdout-form-container').empty(); - $('#stderr-form-container').empty(); - $('#traceback-form-container').empty(); - $('#json-form-container').empty(); - $('#eventview-tabs li:eq(1)').hide(); - $('#eventview-tabs li:eq(2)').hide(); - $('#eventview-tabs li:eq(3)').hide(); - $('#eventview-tabs li:eq(4)').hide(); - $('#eventview-tabs li:eq(5)').hide(); - $('#eventview-tabs li:eq(6)').hide(); - - EventAddTable({ scope: scope, id: 'status-form-container', event: data, section: 'Event' }); - - if (EventAddTable({ scope: scope, id: 'results-form-container', event: data, section: 'Results'})) { - show_tabs = true; - $('#eventview-tabs li:eq(1)').show(); - } - - if (EventAddTable({ scope: scope, id: 'timing-form-container', event: data, section: 'Timing' })) { - show_tabs = true; - $('#eventview-tabs li:eq(2)').show(); - } - - if (data.stdout) { - show_tabs = true; - $('#eventview-tabs li:eq(3)').show(); - EventAddPreFormattedText({ - id: 'stdout-form-container', - val: data.stdout - }); - } - - if (data.stderr) { - show_tabs = true; - $('#eventview-tabs li:eq(4)').show(); - EventAddPreFormattedText({ - id: 'stderr-form-container', - val: data.stderr - }); - } - - if (data.traceback) { - show_tabs = true; - $('#eventview-tabs li:eq(5)').show(); - EventAddPreFormattedText({ - id: 'traceback-form-container', - val: data.traceback - }); - } - - show_tabs = true; - $('#eventview-tabs li:eq(6)').show(); - EventAddPreFormattedText({ - id: 'json-form-container', - val: JSON.stringify(data, null, 2) - }); - - if (!show_tabs) { - $('#eventview-tabs').hide(); - } - - elem = angular.element(document.getElementById('eventviewer-modal-dialog')); - $compile(elem)(scope); - } - - function setButtonMargin() { - var width = ($('.ui-dialog[aria-describedby="eventviewer-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#events-next-button').outerWidth() - 73; - $('#events-next-button').css({'margin-right': width + 'px'}); - } - - function addSpinner() { - var position; - if ($('#event-next-spinner').length > 0) { - $('#event-next-spinner').remove(); - } - position = $('#events-next-button').position(); - $('#events-next-button').after(''); - } - - if (scope.removeModalReady) { - scope.removeModalReady(); - } - scope.removeModalReady = scope.$on('ModalReady', function() { - Wait('stop'); - $('#eventviewer-modal-dialog').dialog('open'); - }); - - if (scope.removeJobReady) { - scope.removeJobReady(); - } - scope.removeEventReady = scope.$on('EventReady', function(e, data) { - var btns; - scope.events = data; - if (event_id) { - // find and show the selected event - data.every(function(row, idx) { - if (parseInt(row.id,10) === parseInt(event_id,10)) { - current_event = idx; - return false; - } - return true; - }); - } - else { - current_event = 0; - } - showEvent(current_event); - - btns = []; - if (scope.events.length > 1) { - btns.push({ - label: "Prev", - onClick: function () { - if (current_event - 1 === 0 && !scope.prev_event_set) { - $('#events-prev-button').prop('disabled', true); - } - if (current_event - 1 < scope.events.length - 1) { - $('#events-next-button').prop('disabled', false); - } - showEvent(current_event - 1); - }, - icon: "fa-chevron-left", - "class": "btn btn-primary", - id: "events-prev-button" - }); - btns.push({ - label: "Next", - onClick: function() { - if (current_event + 1 > 0) { - $('#events-prev-button').prop('disabled', false); - } - if (current_event + 1 >= scope.events.length - 1 && !scope.next_event_set) { - $('#events-next-button').prop('disabled', true); - } - showEvent(current_event + 1); - }, - icon: "fa-chevron-right", - "class": "btn btn-primary", - id: "events-next-button" - }); - } - btns.push({ - label: "OK", - onClick: function() { - scope.modalOK(); - }, - icon: "", - "class": "btn btn-primary", - id: "dialog-ok-button" - }); - - CreateDialog({ - scope: scope, - width: 675, - height: 600, - minWidth: 450, - callback: 'ModalReady', - id: 'eventviewer-modal-dialog', - // onResizeStop: resizeText, - title: ( (title) ? title : 'Host Event' ), - buttons: btns, - closeOnEscape: true, - onResizeStop: function() { - setButtonMargin(); - addSpinner(); - }, - onClose: function() { - try { - scope.$destroy(); - } - catch(e) { - //ignore - } - }, - onOpen: function() { - $('#eventview-tabs a:first').tab('show'); - $('#dialog-ok-button').focus(); - if (scope.events.length > 1 && current_event === 0 && !scope.prev_event_set) { - $('#events-prev-button').prop('disabled', true); - } - if ((current_event === scope.events.length - 1) && !scope.next_event_set) { - $('#events-next-button').prop('disabled', true); - } - if (scope.events.length > 1) { - setButtonMargin(); - addSpinner(); - } + scope.removeShowNextEvent = scope.$on('ShowNextEvent', function(e, data, show_event) { + scope.events = data; + $('#event-next-spinner').hide(400); + if (show_event === 'prev') { + showEvent(scope.events.length - 1); + } + else if (show_event === 'next') { + showEvent(0); } }); - }); - page = (index) ? Math.ceil((index+1)/50) : 1; - url += (/\/$/.test(url)) ? '?' : '&'; - url += (parent_id) ? 'page='+page +'&parent=' + parent_id + '&page_size=50&order=host_name,counter' : 'page_size=50&order=host_name,counter'; + // show scope.events[idx] + function showEvent(idx) { + var show_tabs = false, elem, data; - GetEvent({ - url: url, - scope: scope - }); + if (idx > scope.events.length - 1) { + GetEvent({ + scope: scope, + url: scope.next_event_set, + show_event: 'next' + }); + return; + } - scope.modalOK = function() { - $('#eventviewer-modal-dialog').dialog('close'); - scope.$destroy(); - }; + if (idx < 0) { + GetEvent({ + scope: scope, + url: scope.prev_event_set, + show_event: 'prev' + }); + return; + } - }; - }]) + data = scope.events[idx]; + current_event = idx; - .factory('GetEvent', ['Wait', 'Rest', 'ProcessErrors', - function(Wait, Rest, ProcessErrors) { - return function(params) { - var url = params.url, - scope = params.scope, - show_event = params.show_event, - results= []; + $('#status-form-container').empty(); + $('#results-form-container').empty(); + $('#timing-form-container').empty(); + $('#stdout-form-container').empty(); + $('#stderr-form-container').empty(); + $('#traceback-form-container').empty(); + $('#json-form-container').empty(); + $('#eventview-tabs li:eq(1)').hide(); + $('#eventview-tabs li:eq(2)').hide(); + $('#eventview-tabs li:eq(3)').hide(); + $('#eventview-tabs li:eq(4)').hide(); + $('#eventview-tabs li:eq(5)').hide(); + $('#eventview-tabs li:eq(6)').hide(); - if (show_event) { - $('#event-next-spinner').show(); - } - else { - Wait('start'); - } + EventAddTable({ scope: scope, id: 'status-form-container', event: data, section: 'Event' }); - function getStatus(e) { - return (e.event === "runner_on_unreachable") ? "unreachable" : (e.event === "runner_on_skipped") ? 'skipped' : (e.failed) ? 'failed' : - (e.changed) ? 'changed' : 'ok'; - } + if (EventAddTable({ scope: scope, id: 'results-form-container', event: data, section: 'Results'})) { + show_tabs = true; + $('#eventview-tabs li:eq(1)').show(); + } - Rest.setUrl(url); - Rest.get() - .success( function(data) { + if (EventAddTable({ scope: scope, id: 'timing-form-container', event: data, section: 'Timing' })) { + show_tabs = true; + $('#eventview-tabs li:eq(2)').show(); + } - if(jQuery.isEmptyObject(data)) { - Wait('stop'); - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get event ' + url + '. ' }); + if (data.stdout) { + show_tabs = true; + $('#eventview-tabs li:eq(3)').show(); + EventAddPreFormattedText({ + id: 'stdout-form-container', + val: data.stdout + }); + } + if (data.stderr) { + show_tabs = true; + $('#eventview-tabs li:eq(4)').show(); + EventAddPreFormattedText({ + id: 'stderr-form-container', + val: data.stderr + }); + } + + if (data.traceback) { + show_tabs = true; + $('#eventview-tabs li:eq(5)').show(); + EventAddPreFormattedText({ + id: 'traceback-form-container', + val: data.traceback + }); + } + + show_tabs = true; + $('#eventview-tabs li:eq(6)').show(); + EventAddPreFormattedText({ + id: 'json-form-container', + val: JSON.stringify(data, null, 2) + }); + + if (!show_tabs) { + $('#eventview-tabs').hide(); + } + + elem = angular.element(document.getElementById('eventviewer-modal-dialog')); + $compile(elem)(scope); + } + + function setButtonMargin() { + var width = ($('.ui-dialog[aria-describedby="eventviewer-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#events-next-button').outerWidth() - 73; + $('#events-next-button').css({'margin-right': width + 'px'}); + } + + function addSpinner() { + var position; + if ($('#event-next-spinner').length > 0) { + $('#event-next-spinner').remove(); + } + position = $('#events-next-button').position(); + $('#events-next-button').after(''); + } + + if (scope.removeModalReady) { + scope.removeModalReady(); + } + scope.removeModalReady = scope.$on('ModalReady', function() { + Wait('stop'); + $('#eventviewer-modal-dialog').dialog('open'); + }); + + if (scope.removeJobReady) { + scope.removeJobReady(); + } + scope.removeEventReady = scope.$on('EventReady', function(e, data) { + var btns; + scope.events = data; + if (event_id) { + // find and show the selected event + data.every(function(row, idx) { + if (parseInt(row.id,10) === parseInt(event_id,10)) { + current_event = idx; + return false; + } + return true; + }); } else { - scope.next_event_set = data.next; - scope.prev_event_set = data.previous; - data.results.forEach(function(event) { - var msg, key, event_data = {}; - if (event.event_data.res) { - if (typeof event.event_data.res !== 'object') { - // turn event_data.res into an object - msg = event.event_data.res; - event.event_data.res = {}; - event.event_data.res.msg = msg; + current_event = 0; + } + showEvent(current_event); + + btns = []; + if (scope.events.length > 1) { + btns.push({ + label: "Prev", + onClick: function () { + if (current_event - 1 === 0 && !scope.prev_event_set) { + $('#events-prev-button').prop('disabled', true); } - for (key in event.event_data) { - if (key !== "res") { - event.event_data.res[key] = event.event_data[key]; - } + if (current_event - 1 < scope.events.length - 1) { + $('#events-next-button').prop('disabled', false); } - if (event.event_data.res.ansible_facts) { - // don't show fact gathering results - event.event_data.res.task = "Gathering Facts"; - delete event.event_data.res.ansible_facts; - } - event.event_data.res.status = getStatus(event); - event_data = event.event_data.res; - } - else { - event.event_data.status = getStatus(event); - event_data = event.event_data; - } - // convert results to stdout - if (event_data.results && typeof event_data.results === "object" && Array.isArray(event_data.results)) { - event_data.stdout = ""; - event_data.results.forEach(function(row) { - event_data.stdout += row + "\n"; - }); - delete event_data.results; - } - if (event_data.invocation) { - for (key in event_data.invocation) { - event_data[key] = event_data.invocation[key]; - } - delete event_data.invocation; - } - event_data.play = event.play; - if (event.task) { - event_data.task = event.task; - } - event_data.created = event.created; - event_data.role = event.role; - event_data.host_id = event.host; - event_data.host_name = event.host_name; - if (event_data.host) { - delete event_data.host; - } - event_data.id = event.id; - event_data.parent = event.parent; - event_data.event = (event.event_display) ? event.event_display : event.event; - results.push(event_data); + showEvent(current_event - 1); + }, + icon: "fa-chevron-left", + "class": "btn btn-primary", + id: "events-prev-button" }); - if (show_event) { - scope.$emit('ShowNextEvent', results, show_event); + btns.push({ + label: "Next", + onClick: function() { + if (current_event + 1 > 0) { + $('#events-prev-button').prop('disabled', false); + } + if (current_event + 1 >= scope.events.length - 1 && !scope.next_event_set) { + $('#events-next-button').prop('disabled', true); + } + showEvent(current_event + 1); + }, + icon: "fa-chevron-right", + "class": "btn btn-primary", + id: "events-next-button" + }); + } + btns.push({ + label: "OK", + onClick: function() { + scope.modalOK(); + }, + icon: "", + "class": "btn btn-primary", + id: "dialog-ok-button" + }); + + CreateDialog({ + scope: scope, + width: 675, + height: 600, + minWidth: 450, + callback: 'ModalReady', + id: 'eventviewer-modal-dialog', + // onResizeStop: resizeText, + title: ( (title) ? title : 'Host Event' ), + buttons: btns, + closeOnEscape: true, + onResizeStop: function() { + setButtonMargin(); + addSpinner(); + }, + onClose: function() { + try { + scope.$destroy(); + } + catch(e) { + //ignore + } + }, + onOpen: function() { + $('#eventview-tabs a:first').tab('show'); + $('#dialog-ok-button').focus(); + if (scope.events.length > 1 && current_event === 0 && !scope.prev_event_set) { + $('#events-prev-button').prop('disabled', true); + } + if ((current_event === scope.events.length - 1) && !scope.next_event_set) { + $('#events-next-button').prop('disabled', true); + } + if (scope.events.length > 1) { + setButtonMargin(); + addSpinner(); + } + } + }); + }); + + page = (index) ? Math.ceil((index+1)/50) : 1; + url += (/\/$/.test(url)) ? '?' : '&'; + url += (parent_id) ? 'page='+page +'&parent=' + parent_id + '&page_size=50&order=host_name,counter' : 'page_size=50&order=host_name,counter'; + + GetEvent({ + url: url, + scope: scope + }); + + scope.modalOK = function() { + $('#eventviewer-modal-dialog').dialog('close'); + scope.$destroy(); + }; + + }; + }]) + + .factory('GetEvent', ['Wait', 'Rest', 'ProcessErrors', + function(Wait, Rest, ProcessErrors) { + return function(params) { + var url = params.url, + scope = params.scope, + show_event = params.show_event, + results= []; + + if (show_event) { + $('#event-next-spinner').show(); + } + else { + Wait('start'); + } + + function getStatus(e) { + return (e.event === "runner_on_unreachable") ? "unreachable" : (e.event === "runner_on_skipped") ? 'skipped' : (e.failed) ? 'failed' : + (e.changed) ? 'changed' : 'ok'; + } + + Rest.setUrl(url); + Rest.get() + .success( function(data) { + + if(jQuery.isEmptyObject(data)) { + Wait('stop'); + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get event ' + url + '. ' }); + } else { - scope.$emit('EventReady', results); + scope.next_event_set = data.next; + scope.prev_event_set = data.previous; + data.results.forEach(function(event) { + var msg, key, event_data = {}; + if (event.event_data.res) { + if (typeof event.event_data.res !== 'object') { + // turn event_data.res into an object + msg = event.event_data.res; + event.event_data.res = {}; + event.event_data.res.msg = msg; + } + for (key in event.event_data) { + if (key !== "res") { + event.event_data.res[key] = event.event_data[key]; + } + } + if (event.event_data.res.ansible_facts) { + // don't show fact gathering results + event.event_data.res.task = "Gathering Facts"; + delete event.event_data.res.ansible_facts; + } + event.event_data.res.status = getStatus(event); + event_data = event.event_data.res; + } + else { + event.event_data.status = getStatus(event); + event_data = event.event_data; + } + // convert results to stdout + if (event_data.results && typeof event_data.results === "object" && Array.isArray(event_data.results)) { + event_data.stdout = ""; + event_data.results.forEach(function(row) { + event_data.stdout += row + "\n"; + }); + delete event_data.results; + } + if (event_data.invocation) { + for (key in event_data.invocation) { + event_data[key] = event_data.invocation[key]; + } + delete event_data.invocation; + } + event_data.play = event.play; + if (event.task) { + event_data.task = event.task; + } + event_data.created = event.created; + event_data.role = event.role; + event_data.host_id = event.host; + event_data.host_name = event.host_name; + if (event_data.host) { + delete event_data.host; + } + event_data.id = event.id; + event_data.parent = event.parent; + event_data.event = (event.event_display) ? event.event_display : event.event; + results.push(event_data); + }); + if (show_event) { + scope.$emit('ShowNextEvent', results, show_event); + } + else { + scope.$emit('EventReady', results); + } + } //else statement + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get event ' + url + '. GET returned: ' + status }); + }); + }; + }]) + + .factory('EventAddTable', ['$compile', '$filter', 'Empty', 'EventsViewerForm', function($compile, $filter, Empty, EventsViewerForm) { + return function(params) { + var scope = params.scope, + id = params.id, + event = params.event, + section = params.section, + html = '', e; + + function parseObject(obj) { + // parse nested JSON objects. a mini version of parseJSON without references to the event form object. + var i, key, html = ''; + for (key in obj) { + if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") { + html += "" + key + ":" + obj[key] + ""; + } + else if (typeof obj[key] === "object" && Array.isArray(obj[key])) { + html += "" + key + ":["; + for (i = 0; i < obj[key].length; i++) { + html += obj[key][i] + ","; + } + html = html.replace(/,$/,''); + html += "]\n"; + } + else if (typeof obj[key] === "object") { + html += "" + key + ":\n\n" + parseObject(obj[key]) + "\n
\n\n"; } - } //else statement - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get event ' + url + '. GET returned: ' + status }); - }); - }; - }]) - - .factory('EventAddTable', ['$compile', '$filter', 'Empty', 'EventsViewerForm', function($compile, $filter, Empty, EventsViewerForm) { - return function(params) { - var scope = params.scope, - id = params.id, - event = params.event, - section = params.section, - html = '', e; - - function parseObject(obj) { - // parse nested JSON objects. a mini version of parseJSON without references to the event form object. - var i, key, html = ''; - for (key in obj) { - if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") { - html += "" + key + ":" + obj[key] + ""; } - else if (typeof obj[key] === "object" && Array.isArray(obj[key])) { - html += "" + key + ":["; - for (i = 0; i < obj[key].length; i++) { - html += obj[key][i] + ","; + return html; + } + + function parseItem(itm, key, label) { + var i, html = ''; + if (Empty(itm)) { + // exclude empty items + } + else if (typeof itm === "boolean" || typeof itm === "number" || typeof itm === "string") { + html += "" + label + ":"; + if (key === "status") { + html += " " + itm; + } + else if (key === "start" || key === "end" || key === "created") { + if (!/Z$/.test(itm)) { + itm = itm.replace(/\ /,'T') + 'Z'; + html += $filter('date')(itm, 'MM/dd/yy HH:mm:ss.sss'); + } + else { + html += $filter('date')(itm, 'MM/dd/yy HH:mm:ss'); + } + } + else if (key === "host_name" && event.host_id) { + html += "" + itm + ""; + } + else { + if( typeof itm === "string"){ + if(itm.indexOf('<') > -1 || itm.indexOf('>') > -1){ + itm = itm.replace(//g, ">"); + } + } + html += "" + itm + ""; + } + + html += "\n"; + } + else if (typeof itm === "object" && Array.isArray(itm)) { + html += "" + label + ":["; + for (i = 0; i < itm.length; i++) { + html += itm[i] + ","; } html = html.replace(/,$/,''); html += "]\n"; } - else if (typeof obj[key] === "object") { - html += "" + key + ":\n\n" + parseObject(obj[key]) + "\n
\n\n"; + else if (typeof itm === "object") { + html += "" + label + ":\n\n" + parseObject(itm) + "\n
\n\n"; } + return html; } - return html; - } - function parseItem(itm, key, label) { - var i, html = ''; - if (Empty(itm)) { - // exclude empty items - } - else if (typeof itm === "boolean" || typeof itm === "number" || typeof itm === "string") { - html += "" + label + ":"; - if (key === "status") { - html += " " + itm; - } - else if (key === "start" || key === "end" || key === "created") { - if (!/Z$/.test(itm)) { - itm = itm.replace(/\ /,'T') + 'Z'; - html += $filter('date')(itm, 'MM/dd/yy HH:mm:ss.sss'); - } - else { - html += $filter('date')(itm, 'MM/dd/yy HH:mm:ss'); - } - } - else if (key === "host_name" && event.host_id) { - html += "" + itm + ""; - } - else { - if( typeof itm === "string"){ - if(itm.indexOf('<') > -1 || itm.indexOf('>') > -1){ - itm = itm.replace(//g, ">"); + function parseJSON(obj) { + var h, html = '', key, keys, found = false, string_warnings = "", string_cmd = ""; + if (typeof obj === "object") { + html += "\n"; + html += "\n"; + keys = []; + for (key in EventsViewerForm.fields) { + if (EventsViewerForm.fields[key].section === section) { + keys.push(key); } } - html += "" + itm + ""; - } - - html += "\n"; - } - else if (typeof itm === "object" && Array.isArray(itm)) { - html += "\n"; - } - else if (typeof itm === "object") { - html += "\n"; - } - return html; - } - - function parseJSON(obj) { - var h, html = '', key, keys, found = false, string_warnings = "", string_cmd = ""; - if (typeof obj === "object") { - html += "
" + label + ":["; - for (i = 0; i < itm.length; i++) { - html += itm[i] + ","; - } - html = html.replace(/,$/,''); - html += "]
" + label + ":\n\n" + parseObject(itm) + "\n
\n
\n"; - html += "\n"; - keys = []; - for (key in EventsViewerForm.fields) { - if (EventsViewerForm.fields[key].section === section) { - keys.push(key); - } - } - keys.forEach(function(key) { - var h, label; - label = EventsViewerForm.fields[key].label; - h = parseItem(obj[key], key, label); - if (h) { - html += h; - found = true; - } - }); - if (section === 'Results') { - // Add to result fields that might not be found in the form object. - for (key in obj) { - h = ''; - if (key !== 'host_id' && key !== 'parent' && key !== 'event' && key !== 'src' && key !== 'md5sum' && - key !== 'stdout' && key !== 'traceback' && key !== 'stderr' && key !== 'cmd' && key !=='changed' && key !== "verbose_override" && - key !== 'feature_result' && key !== 'warnings') { - if (!EventsViewerForm.fields[key]) { - h = parseItem(obj[key], key, key); - if (h) { - html += h; - found = true; + keys.forEach(function(key) { + var h, label; + label = EventsViewerForm.fields[key].label; + h = parseItem(obj[key], key, label); + if (h) { + html += h; + found = true; + } + }); + if (section === 'Results') { + // Add to result fields that might not be found in the form object. + for (key in obj) { + h = ''; + if (key !== 'host_id' && key !== 'parent' && key !== 'event' && key !== 'src' && key !== 'md5sum' && + key !== 'stdout' && key !== 'traceback' && key !== 'stderr' && key !== 'cmd' && key !=='changed' && key !== "verbose_override" && + key !== 'feature_result' && key !== 'warnings') { + if (!EventsViewerForm.fields[key]) { + h = parseItem(obj[key], key, key); + if (h) { + html += h; + found = true; + } } - } - } else if (key === 'cmd') { - // only show cmd if it's a cmd that was run - if (!EventsViewerForm.fields[key] && obj[key].length > 0) { - // include the label head Shell Command instead of CMD in the modal - string_cmd += obj[key].join(" "); - h = parseItem(string_cmd, key, "Shell Command"); - if (h) { - html += h; - found = true; + } else if (key === 'cmd') { + // only show cmd if it's a cmd that was run + if (!EventsViewerForm.fields[key] && obj[key].length > 0) { + // include the label head Shell Command instead of CMD in the modal + string_cmd += obj[key].join(" "); + h = parseItem(string_cmd, key, "Shell Command"); + if (h) { + html += h; + found = true; + } } - } - } else if (key === 'warnings') { - if (!EventsViewerForm.fields[key] && obj[key].length > 0) { - string_warnings += obj[key].join(" "); - h = parseItem(string_warnings, key, "Warnings"); - if (h) { - html += h; - found = true; + } else if (key === 'warnings') { + if (!EventsViewerForm.fields[key] && obj[key].length > 0) { + string_warnings += obj[key].join(" "); + h = parseItem(string_warnings, key, "Warnings"); + if (h) { + html += h; + found = true; + } } } } } + html += "\n"; + html += "
\n"; } - html += "\n"; - html += "\n"; + return (found) ? html : ''; } - return (found) ? html : ''; - } - html = parseJSON(event); + html = parseJSON(event); - e = angular.element(document.getElementById(id)); - e.empty(); - if (html) { - e.html(html); - $compile(e)(scope); - } - return (html) ? true : false; - }; - }]) - - .factory('EventAddTextarea', [ function() { - return function(params) { - var container_id = params.container_id, - val = params.val, - fld_id = params.fld_id, - html; - html = "
\n" + - "" + - "
\n"; - $('#' + container_id).empty().html(html); - }; - }]) - - .factory('EventAddPreFormattedText', [function() { - return function(params) { - var id = params.id, - val = params.val, - html; - if( typeof val === "string"){ - if(val.indexOf('<') > -1 || val.indexOf('>') > -1){ - val = val.replace(//g, ">"); + e = angular.element(document.getElementById(id)); + e.empty(); + if (html) { + e.html(html); + $compile(e)(scope); } - } - html = "
" + val + "
\n"; - $('#' + id).empty().html(html); - }; - }]); + return (html) ? true : false; + }; + }]) + + .factory('EventAddTextarea', [ function() { + return function(params) { + var container_id = params.container_id, + val = params.val, + fld_id = params.fld_id, + html; + html = "
\n" + + "" + + "
\n"; + $('#' + container_id).empty().html(html); + }; + }]) + + .factory('EventAddPreFormattedText', [function() { + return function(params) { + var id = params.id, + val = params.val, + html; + if( typeof val === "string"){ + if(val.indexOf('<') > -1 || val.indexOf('>') > -1){ + val = val.replace(//g, ">"); + } + } + html = "
" + val + "
\n"; + $('#' + id).empty().html(html); + }; + }]); diff --git a/awx/ui/static/js/helpers/Events.js b/awx/ui/static/js/helpers/Events.js index 8532f3c5a9..3b9909f7f0 100644 --- a/awx/ui/static/js/helpers/Events.js +++ b/awx/ui/static/js/helpers/Events.js @@ -12,29 +12,53 @@ * @description EventView - show the job_events form in a modal dialog */ +export default + angular.module('EventsHelper', ['RestServices', 'Utilities', 'JobEventDataDefinition', 'JobEventsFormDefinition']) -angular.module('EventsHelper', ['RestServices', 'Utilities', 'JobEventDataDefinition', 'JobEventsFormDefinition']) + .factory('EventView', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GenerateForm', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'JobEventDataForm', 'Empty', 'JobEventsForm', + function ($rootScope, $location, $log, $routeParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, + FormatDate, JobEventDataForm, Empty, JobEventsForm) { + return function (params) { - .factory('EventView', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'JobEventDataForm', 'Empty', 'JobEventsForm', - function ($rootScope, $location, $log, $routeParams, 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 + '/'; - 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; - // 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 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') { + 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': @@ -44,196 +68,172 @@ angular.module('EventsHelper', ['RestServices', 'Utilities', 'JobEventDataDefini 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': - case 'rc': - delete form.fields[fld]; + 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; } } - } - 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, { + // load the form + scope = generator.inject(form, { mode: 'edit', modal: true, - related: false, - modal_selector: '#form-modal2', - modal_body_id: 'form-modal2-body', - modal_title_id: 'formModal2Header' + related: false }); 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"); + scope.formModalAction = function () { + $('#form-modal').modal("hide"); }; - $('#form-modal2 .btn-success').removeClass('btn-success').addClass('btn-none'); - }; + 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, ''); - if (typeof data.event_data.res === 'string') { - scope.traceback = data.event_data.res; - } + // 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'); + }; - 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 (typeof data.event_data.res === 'string') { + scope.traceback = data.event_data.res; } - } - if (!scope.$$phase) { - scope.$digest(); - } + 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]; + } - }) - .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 }); - }); - }; - } - ]); \ No newline at end of file + 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/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index 5abc73cb20..1f68ff75ae 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -6,18 +6,20 @@ * Routines that handle group add/edit/delete on the Inventory tree widget. * */ - +'use strict'; /** * @ngdoc function * @name helpers.function:Groups * @description inventory tree widget add/edit/delete */ + +export default angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'GroupListDefinition', 'SearchHelper', -'PaginationHelpers', 'ListGenerator', 'AuthService', 'GroupsHelper', 'InventoryHelper', 'SelectionHelper', -'JobSubmissionHelper', 'RefreshHelper', 'PromptDialog', 'CredentialsListDefinition', 'InventoryTree', -'InventoryStatusDefinition', 'VariablesHelper', 'SchedulesListDefinition', 'SourceFormDefinition', 'LogViewerHelper', -'SchedulesHelper' + 'PaginationHelpers', 'ListGenerator', 'AuthService', 'GroupsHelper', 'InventoryHelper', 'SelectionHelper', + 'JobSubmissionHelper', 'RefreshHelper', 'PromptDialog', 'CredentialsListDefinition', 'InventoryTree', + 'InventoryStatusDefinition', 'VariablesHelper', 'SchedulesListDefinition', 'SourceFormDefinition', 'LogViewerHelper', + 'SchedulesHelper' ]) /** @@ -26,35 +28,35 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GetSourceTypeOptions', ['Rest', 'ProcessErrors', 'GetBasePath', - function (Rest, ProcessErrors, GetBasePath) { - return function (params) { - var scope = params.scope, - variable = params.variable; + 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][0] === '') ? 'Manual' : choices[i][1]), - value: (choices[i][0] === '') ? 'manual' : choices[i][0] - }); - } - } - 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 - }); - }); - } - }; - } + 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][0] === '') ? 'Manual' : choices[i][1]), + value: (choices[i][0] === '') ? 'manual' : choices[i][0] + }); + } + } + 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 + }); + }); + } + }; + } ]) /** @@ -63,47 +65,47 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('ViewUpdateStatus', ['Rest', 'ProcessErrors', 'GetBasePath', 'Alert', 'Wait', 'Empty', 'Find', 'LogViewer', - function (Rest, ProcessErrors, GetBasePath, Alert, Wait, Empty, Find, LogViewer) { - return function (params) { + function (Rest, ProcessErrors, GetBasePath, Alert, Wait, Empty, Find, LogViewer) { + return function (params) { - var scope = params.scope, - group_id = params.group_id, - group = Find({ list: scope.groups, key: 'id', val: group_id }); + 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, url) { - LogViewer({ - scope: scope, - url: url - }); - }); + if (scope.removeSourceReady) { + scope.removeSourceReady(); + } + scope.removeSourceReady = scope.$on('SourceReady', function(e, url) { + LogViewer({ + scope: scope, + url: url + }); + }); - 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'); - } else { - Wait('start'); - Rest.setUrl(group.related.inventory_source); - Rest.get() - .success(function (data) { - var url = (data.related.current_update) ? data.related.current_update : data.related.last_update; - scope.$emit('SourceReady', url); - }) - .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 }); - }); - } - } + 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'); + } else { + Wait('start'); + Rest.setUrl(group.related.inventory_source); + Rest.get() + .success(function (data) { + var url = (data.related.current_update) ? data.related.current_update : data.related.last_update; + scope.$emit('SourceReady', url); + }) + .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 }); + }); + } + } - }; - } + }; + } ]) /** @@ -113,38 +115,38 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' */ .factory('GetHostsStatusMsg', [ function () { - return function (params) { + return function (params) { - var active_failures = params.active_failures, - total_hosts = params.total_hosts, - tip, failures, html_class; + var active_failures = params.active_failures, + total_hosts = params.total_hosts, + tip, failures, html_class; - // Return values for use on host status indicator + // 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; + 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 { - 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'; - } + // 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 - }; + return { + tooltip: tip, + failures: failures, + 'class': html_class }; - } + }; +} ]) /** @@ -153,78 +155,78 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GetSyncStatusMsg', [ 'Empty', - function (Empty) { - return function (params) { + 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', - stat, stat_class, status_tip; + var status = params.status, + source = params.source, + has_inventory_sources = params.has_inventory_sources, + launch_class = '', + launch_tip = 'Start sync process', + stat, stat_class, status_tip; - stat = status; - stat_class = stat; + 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; - } + 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 && 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.'; - } + 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 - }; - }; - } + return { + "class": stat_class, + "tooltip": status_tip, + "status": stat, + "launch_class": launch_class, + "launch_tip": launch_tip + }; + }; + } ]) /** @@ -233,99 +235,99 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('SourceChange', ['GetBasePath', 'CredentialList', 'LookUpInit', 'Empty', 'Wait', 'ParseTypeChange', 'CustomInventoryList' , - function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange, CustomInventoryList) { - return function (params) { + function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange, CustomInventoryList) { + return function (params) { - var scope = params.scope, - form = params.form, - kind, url, callback, invUrl; + var scope = params.scope, + form = params.form, + kind, url, callback, invUrl; - if (!Empty(scope.source)) { - if (scope.source.value === 'file') { - scope.sourcePathRequired = true; - } else { - scope.sourcePathRequired = false; - // reset fields - scope.source_path = ''; - scope[form.name + '_form'].source_path.$setValidity('required', true); - } - if (scope.source.value === 'rax') { - scope.source_region_choices = scope.rax_regions; - //$('#s2id_group_source_regions').select2('data', []); - $('#s2id_source_source_regions').select2('data', [{ - id: 'all', - text: 'All' - }]); - $('#source_form').addClass('squeeze'); - } else if (scope.source.value === 'ec2') { - scope.source_region_choices = scope.ec2_regions; - $('#s2id_source_source_regions').select2('data', [{ - id: 'all', - text: 'All' - }]); - scope.group_by_choices = scope.ec2_group_by; - $('#s2id_group_by').select2('data', []); - $('#source_form').addClass('squeeze'); - } else if (scope.source.value === 'gce') { - scope.source_region_choices = scope.gce_regions; - //$('#s2id_group_source_regions').select2('data', []); - $('#s2id_source_source_regions').select2('data', [{ - id: 'all', - text: 'All' - }]); - $('#source_form').addClass('squeeze'); - } else if (scope.source.value === 'azure') { - scope.source_region_choices = scope.azure_regions; - //$('#s2id_group_source_regions').select2('data', []); - $('#s2id_source_source_regions').select2('data', [{ - id: 'all', - text: 'All' - }]); - $('#source_form').addClass('squeeze'); - } - if(scope.source.value==="custom"){ - // need to filter the possible custom scripts by the organization defined for the current inventory - invUrl = GetBasePath('inventory_scripts') + '?organization='+scope.$parent.inventory.organization; - LookUpInit({ - url: invUrl, - scope: scope, - form: form, - hdr: "Select Custom Inventory", - list: CustomInventoryList, - field: 'source_script', - input_type: 'radio' - }); - scope.extra_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars; - ParseTypeChange({ scope: scope, variable: 'extra_vars', parse_variable: form.fields.extra_vars.parseTypeName, - field_id: 'source_extra_vars', onReady: callback }); - } - if(scope.source.value==="vmware"){ - scope.inventory_variables = (Empty(scope.source_vars)) ? "---" : scope.source_vars; - ParseTypeChange({ scope: scope, variable: 'inventory_variables', parse_variable: form.fields.inventory_variables.parseTypeName, - field_id: 'source_inventory_variables', onReady: callback }); - } - if (scope.source.value === 'rax' || scope.source.value === 'ec2'|| scope.source.value==='gce' || scope.source.value === 'azure' || scope.source.value === 'vmware') { - kind = (scope.source.value === 'rax') ? 'rax' : (scope.source.value==='gce') ? 'gce' : (scope.source.value==='azure') ? 'azure' : (scope.source.value === 'vmware') ? 'vmware' : 'aws' ; - url = GetBasePath('credentials') + '?cloud=true&kind=' + kind; - LookUpInit({ - url: url, - scope: scope, - form: form, - list: CredentialList, - field: 'credential', - input_type: "radio" - }); - if ($('#group_tabs .active a').text() === 'Source' && (scope.source.value === 'ec2' )) { - callback = function(){ Wait('stop'); }; - Wait('start'); - scope.source_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars; - ParseTypeChange({ scope: scope, variable: 'source_vars', parse_variable: form.fields.source_vars.parseTypeName, - field_id: 'source_source_vars', onReady: callback }); - } - } - } - }; - } + if (!Empty(scope.source)) { + if (scope.source.value === 'file') { + scope.sourcePathRequired = true; + } else { + scope.sourcePathRequired = false; + // reset fields + scope.source_path = ''; + scope[form.name + '_form'].source_path.$setValidity('required', true); + } + if (scope.source.value === 'rax') { + scope.source_region_choices = scope.rax_regions; + //$('#s2id_group_source_regions').select2('data', []); + $('#s2id_source_source_regions').select2('data', [{ + id: 'all', + text: 'All' + }]); + $('#source_form').addClass('squeeze'); + } else if (scope.source.value === 'ec2') { + scope.source_region_choices = scope.ec2_regions; + $('#s2id_source_source_regions').select2('data', [{ + id: 'all', + text: 'All' + }]); + scope.group_by_choices = scope.ec2_group_by; + $('#s2id_group_by').select2('data', []); + $('#source_form').addClass('squeeze'); + } else if (scope.source.value === 'gce') { + scope.source_region_choices = scope.gce_regions; + //$('#s2id_group_source_regions').select2('data', []); + $('#s2id_source_source_regions').select2('data', [{ + id: 'all', + text: 'All' + }]); + $('#source_form').addClass('squeeze'); + } else if (scope.source.value === 'azure') { + scope.source_region_choices = scope.azure_regions; + //$('#s2id_group_source_regions').select2('data', []); + $('#s2id_source_source_regions').select2('data', [{ + id: 'all', + text: 'All' + }]); + $('#source_form').addClass('squeeze'); + } + if(scope.source.value==="custom"){ + // need to filter the possible custom scripts by the organization defined for the current inventory + invUrl = GetBasePath('inventory_scripts') + '?organization='+scope.$parent.inventory.organization; + LookUpInit({ + url: invUrl, + scope: scope, + form: form, + hdr: "Select Custom Inventory", + list: CustomInventoryList, + field: 'source_script', + input_type: 'radio' + }); + scope.extra_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars; + ParseTypeChange({ scope: scope, variable: 'extra_vars', parse_variable: form.fields.extra_vars.parseTypeName, + field_id: 'source_extra_vars', onReady: callback }); + } + if(scope.source.value==="vmware"){ + scope.inventory_variables = (Empty(scope.source_vars)) ? "---" : scope.source_vars; + ParseTypeChange({ scope: scope, variable: 'inventory_variables', parse_variable: form.fields.inventory_variables.parseTypeName, + field_id: 'source_inventory_variables', onReady: callback }); + } + if (scope.source.value === 'rax' || scope.source.value === 'ec2'|| scope.source.value==='gce' || scope.source.value === 'azure' || scope.source.value === 'vmware') { + kind = (scope.source.value === 'rax') ? 'rax' : (scope.source.value==='gce') ? 'gce' : (scope.source.value==='azure') ? 'azure' : (scope.source.value === 'vmware') ? 'vmware' : 'aws' ; + url = GetBasePath('credentials') + '?cloud=true&kind=' + kind; + LookUpInit({ + url: url, + scope: scope, + form: form, + list: CredentialList, + field: 'credential', + input_type: "radio" + }); + if ($('#group_tabs .active a').text() === 'Source' && (scope.source.value === 'ec2' )) { + callback = function(){ Wait('stop'); }; + Wait('start'); + scope.source_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars; + ParseTypeChange({ scope: scope, variable: 'source_vars', parse_variable: form.fields.source_vars.parseTypeName, + field_id: 'source_source_vars', onReady: callback }); + } + } + } + }; + } ]) /** @@ -334,82 +336,82 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GroupsCancelUpdate', ['Empty', 'Rest', 'ProcessErrors', 'Alert', 'Wait', 'Find', - function (Empty, Rest, ProcessErrors, Alert, Wait, Find) { - return function (params) { + function (Empty, Rest, ProcessErrors, Alert, Wait, Find) { + return function (params) { - var scope = params.scope, - id = params.id, - group = params.group; + 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.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 - }); - }); - }); + 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; - } + // 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 - }); - }); - } - }; - } + 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 + }); + }); + } + }; + } ]) /** @@ -418,107 +420,107 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GroupsScheduleListInit', ['GroupsScheduleEdit', 'SchedulesList', 'GenerateList', 'SearchInit', 'PaginateInit', 'Rest', -'PageRangeSetup', 'Wait', 'ProcessErrors', 'Find', 'ToggleSchedule', 'DeleteSchedule', 'GetBasePath', 'SchedulesListInit', - function(GroupsScheduleEdit, SchedulesList, GenerateList, SearchInit, PaginateInit, Rest, PageRangeSetup, Wait, ProcessErrors, Find, - ToggleSchedule, DeleteSchedule, GetBasePath, SchedulesListInit) { - return function(params) { - var schedule_scope = params.scope, - url = params.url, - list; + 'PageRangeSetup', 'Wait', 'ProcessErrors', 'Find', 'ToggleSchedule', 'DeleteSchedule', 'GetBasePath', 'SchedulesListInit', + function(GroupsScheduleEdit, SchedulesList, GenerateList, SearchInit, PaginateInit, Rest, PageRangeSetup, Wait, ProcessErrors, Find, + ToggleSchedule, DeleteSchedule, GetBasePath, SchedulesListInit) { + return function(params) { + var schedule_scope = params.scope, + url = params.url, + list; - // Clean up - $('#schedules-list').hide().empty(); - $('#schedules-form-container').hide(); - $('#schedules-form').empty(); - $('.tooltip').each(function () { - $(this).remove(); - }); - $('.popover').each(function () { - $(this).remove(); - }); + // Clean up + $('#schedules-list').hide().empty(); + $('#schedules-form-container').hide(); + $('#schedules-form').empty(); + $('.tooltip').each(function () { + $(this).remove(); + }); + $('.popover').each(function () { + $(this).remove(); + }); - // Add schedules list - list = angular.copy(SchedulesList); - delete list.fields.dtend; - delete list.actions.stream; - list.well = false; - GenerateList.inject(list, { - mode: 'edit', - id: 'schedules-list', - breadCrumbs: false, - searchSize: 'col-lg-6 col-md-5 col-sm-5 col-xs-5', - scope: schedule_scope - }); + // Add schedules list + list = angular.copy(SchedulesList); + delete list.fields.dtend; + delete list.actions.stream; + list.well = false; + GenerateList.inject(list, { + mode: 'edit', + id: 'schedules-list', + breadCrumbs: false, + searchSize: 'col-lg-6 col-md-5 col-sm-5 col-xs-5', + scope: schedule_scope + }); - $('#schedules-list').show(); + $('#schedules-list').show(); - // Removing screws up /home/groups page - // if (schedule_scope.removePostRefresh) { - // schedule_scope.removePostRefresh(); - //} - schedule_scope.removePostRefresh = schedule_scope.$on('PostRefresh', function() { - SchedulesListInit({ - scope: schedule_scope, - list: list, - choices: null - }); - }); - SearchInit({ - scope: schedule_scope, - set: 'schedules', - list: SchedulesList, - url: url - }); - PaginateInit({ - scope: schedule_scope, - list: SchedulesList, - url: url, - pageSize: 5 - }); - schedule_scope.search(list.iterator); + // Removing screws up /home/groups page + // if (schedule_scope.removePostRefresh) { + // schedule_scope.removePostRefresh(); + //} + schedule_scope.removePostRefresh = schedule_scope.$on('PostRefresh', function() { + SchedulesListInit({ + scope: schedule_scope, + list: list, + choices: null + }); + }); + SearchInit({ + scope: schedule_scope, + set: 'schedules', + list: SchedulesList, + url: url + }); + PaginateInit({ + scope: schedule_scope, + list: SchedulesList, + url: url, + pageSize: 5 + }); + schedule_scope.search(list.iterator); - schedule_scope.refreshSchedules = function() { - schedule_scope.search(list.iterator); - }; + schedule_scope.refreshSchedules = function() { + schedule_scope.search(list.iterator); + }; - schedule_scope.editSchedule = function(id) { - GroupsScheduleEdit({ scope: schedule_scope, mode: 'edit', url: GetBasePath('schedules') + id + '/' }); - }; + schedule_scope.editSchedule = function(id) { + GroupsScheduleEdit({ scope: schedule_scope, mode: 'edit', url: GetBasePath('schedules') + id + '/' }); + }; - schedule_scope.addSchedule = function() { - GroupsScheduleEdit({ scope: schedule_scope, mode: 'add', url: url }); - }; + schedule_scope.addSchedule = function() { + GroupsScheduleEdit({ scope: schedule_scope, mode: 'add', url: url }); + }; - if (schedule_scope.removeSchedulesRefresh) { - schedule_scope.removeSchedulesRefresh(); - } - schedule_scope.removeSchedulesRefresh = schedule_scope.$on('SchedulesRefresh', function() { - schedule_scope.search(list.iterator); - }); + if (schedule_scope.removeSchedulesRefresh) { + schedule_scope.removeSchedulesRefresh(); + } + schedule_scope.removeSchedulesRefresh = schedule_scope.$on('SchedulesRefresh', function() { + schedule_scope.search(list.iterator); + }); - schedule_scope.toggleSchedule = function(event, id) { - try { - $(event.target).tooltip('hide'); - } - catch(e) { - // ignore - } - ToggleSchedule({ - scope: schedule_scope, - id: id, - callback: 'SchedulesRefresh' - }); - }; + schedule_scope.toggleSchedule = function(event, id) { + try { + $(event.target).tooltip('hide'); + } + catch(e) { + // ignore + } + ToggleSchedule({ + scope: schedule_scope, + id: id, + callback: 'SchedulesRefresh' + }); + }; - schedule_scope.deleteSchedule = function(id) { - DeleteSchedule({ - scope: schedule_scope, - id: id, - callback: 'SchedulesRefresh' - }); - }; - }; - } + schedule_scope.deleteSchedule = function(id) { + DeleteSchedule({ + scope: schedule_scope, + id: id, + callback: 'SchedulesRefresh' + }); + }; + }; + } ]) /** @@ -528,12 +530,12 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' */ .factory('SetSchedulesInnerDialogSize', [ function() { - return function() { - var height = $('#group-modal-dialog').outerHeight() - $('#group_tabs').outerHeight() - 25; - height = height - 110 - $('#schedules-buttons').outerHeight(); - $('#schedules-form-container-body').height(height); - }; - } + return function() { + var height = $('#group-modal-dialog').outerHeight() - $('#group_tabs').outerHeight() - 25; + height = height - 110 - $('#schedules-buttons').outerHeight(); + $('#schedules-form-container-body').height(height); + }; +} ]) /** @@ -542,150 +544,150 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GroupsScheduleEdit', ['$compile','SchedulerInit', 'Rest', 'Wait', 'SetSchedulesInnerDialogSize', 'SchedulePost', 'ProcessErrors', - function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, SchedulePost, ProcessErrors) { - return function(params) { - var parent_scope = params.scope, - mode = params.mode, // 'add' or 'edit' - url = params.url, - scope = parent_scope.$new(), - schedule = {}, - scheduler, - target, - showForm, - list, - detail, - restoreList, - container, - elem; + function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, SchedulePost, ProcessErrors) { + return function(params) { + var parent_scope = params.scope, + mode = params.mode, // 'add' or 'edit' + url = params.url, + scope = parent_scope.$new(), + schedule = {}, + scheduler, + target, + showForm, + list, + detail, + restoreList, + container, + elem; - Wait('start'); - detail = $('#schedules-detail').hide(); - list = $('#schedules-list'); - target = $('#schedules-form'); - container = $('#schedules-form-container'); - // Clean up any lingering stuff - container.hide(); - target.empty(); - $('.tooltip').each(function () { - $(this).remove(); - }); - $('.popover').each(function () { - $(this).remove(); - }); + Wait('start'); + detail = $('#schedules-detail').hide(); + list = $('#schedules-list'); + target = $('#schedules-form'); + container = $('#schedules-form-container'); + // Clean up any lingering stuff + container.hide(); + target.empty(); + $('.tooltip').each(function () { + $(this).remove(); + }); + $('.popover').each(function () { + $(this).remove(); + }); - elem = angular.element(document.getElementById('schedules-form-container')); - $compile(elem)(scope); + elem = angular.element(document.getElementById('schedules-form-container')); + $compile(elem)(scope); - if (scope.removeScheduleReady) { - scope.removeScheduleReady(); - } - scope.removeScheduleReady = scope.$on('ScheduleReady', function() { - // Insert the scheduler widget into the hidden div - scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); - scheduler.inject('schedules-form', false); - scheduler.injectDetail('schedules-detail', false); - scheduler.clear(); - scope.formShowing = true; - scope.showRRuleDetail = false; - scope.schedulesTitle = (mode === 'edit') ? 'Edit Schedule' : 'Create Schedule'; + if (scope.removeScheduleReady) { + scope.removeScheduleReady(); + } + scope.removeScheduleReady = scope.$on('ScheduleReady', function() { + // Insert the scheduler widget into the hidden div + scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); + scheduler.inject('schedules-form', false); + scheduler.injectDetail('schedules-detail', false); + scheduler.clear(); + scope.formShowing = true; + scope.showRRuleDetail = false; + scope.schedulesTitle = (mode === 'edit') ? 'Edit Schedule' : 'Create Schedule'; - // display the scheduler widget - showForm = function() { - Wait('stop'); - $('#schedules-overlay').width($('#schedules-tab') - .width()).height($('#schedules-tab').height()).show(); - container.width($('#schedules-tab').width() - 18); - SetSchedulesInnerDialogSize(); - container.show('slide', { direction: 'left' }, 300); - $('#group-save-button').prop('disabled', true); - target.show(); - if (mode === 'edit') { - scope.$apply(function() { - scheduler.setRRule(schedule.rrule); - scheduler.setName(schedule.name); - }); - } - }; - setTimeout(function() { showForm(); }, 1000); - }); + // display the scheduler widget + showForm = function() { + Wait('stop'); + $('#schedules-overlay').width($('#schedules-tab') + .width()).height($('#schedules-tab').height()).show(); + container.width($('#schedules-tab').width() - 18); + SetSchedulesInnerDialogSize(); + container.show('slide', { direction: 'left' }, 300); + $('#group-save-button').prop('disabled', true); + target.show(); + if (mode === 'edit') { + scope.$apply(function() { + scheduler.setRRule(schedule.rrule); + scheduler.setName(schedule.name); + }); + } + }; + setTimeout(function() { showForm(); }, 1000); + }); - restoreList = function() { - $('#group-save-button').prop('disabled', false); - list.show('slide', { direction: 'right' }, 500); - $('#schedules-overlay').width($('#schedules-tab').width()).height($('#schedules-tab').height()).hide(); - parent_scope.refreshSchedules(); - }; + restoreList = function() { + $('#group-save-button').prop('disabled', false); + list.show('slide', { direction: 'right' }, 500); + $('#schedules-overlay').width($('#schedules-tab').width()).height($('#schedules-tab').height()).hide(); + parent_scope.refreshSchedules(); + }; - scope.showScheduleDetail = function() { - if (scope.formShowing) { - if (scheduler.isValid()) { - detail.width($('#schedules-form').width()).height($('#schedules-form').height()); - target.hide(); - detail.show(); - scope.formShowing = false; - } - } - else { - detail.hide(); - target.show(); - scope.formShowing = true; - } - }; + scope.showScheduleDetail = function() { + if (scope.formShowing) { + if (scheduler.isValid()) { + detail.width($('#schedules-form').width()).height($('#schedules-form').height()); + target.hide(); + detail.show(); + scope.formShowing = false; + } + } + else { + detail.hide(); + target.show(); + scope.formShowing = true; + } + }; - if (scope.removeScheduleSaved) { - scope.removeScheduleSaved(); - } - scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { - Wait('stop'); - container.hide('slide', { direction: 'right' }, 500, restoreList); - scope.$destroy(); - }); + if (scope.removeScheduleSaved) { + scope.removeScheduleSaved(); + } + scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { + Wait('stop'); + container.hide('slide', { direction: 'right' }, 500, restoreList); + scope.$destroy(); + }); - scope.saveScheduleForm = function() { - if (scheduler.isValid()) { - scope.schedulerIsValid = true; - SchedulePost({ - scope: scope, - url: url, - scheduler: scheduler, - callback: 'ScheduleSaved', - mode: mode, - schedule: schedule - }); - } - else { - scope.schedulerIsValid = false; - } - }; + scope.saveScheduleForm = function() { + if (scheduler.isValid()) { + scope.schedulerIsValid = true; + SchedulePost({ + scope: scope, + url: url, + scheduler: scheduler, + callback: 'ScheduleSaved', + mode: mode, + schedule: schedule + }); + } + else { + scope.schedulerIsValid = false; + } + }; - scope.cancelScheduleForm = function() { - container.hide('slide', { direction: 'right' }, 500, restoreList); - scope.$destroy(); - }; + scope.cancelScheduleForm = function() { + container.hide('slide', { direction: 'right' }, 500, restoreList); + scope.$destroy(); + }; - if (mode === 'edit') { - // Get the existing record - Rest.setUrl(url); - Rest.get() - .success(function(data) { - schedule = data; - 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.$emit('ScheduleReady'); - }) - .error(function(data,status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); - }); - } - else { - scope.$emit('ScheduleReady'); - } - }; - } + if (mode === 'edit') { + // Get the existing record + Rest.setUrl(url); + Rest.get() + .success(function(data) { + schedule = data; + 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.$emit('ScheduleReady'); + }) + .error(function(data,status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get: ' + url + ' GET returned: ' + status }); + }); + } + else { + scope.$emit('ScheduleReady'); + } + }; + } ]) /** @@ -694,724 +696,724 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', '$compile', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', -'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate', -'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find', 'WatchInventoryWindowResize', -'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize', - function ($rootScope, $location, $log, $routeParams, $compile, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty, Wait, - GetChoices, UpdateGroup, SourceChange, Find, WatchInventoryWindowResize, ParseVariableString, ToJSON, GroupsScheduleListInit, - SourceForm, SetSchedulesInnerDialogSize) { - return function (params) { + 'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate', + 'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find', 'WatchInventoryWindowResize', + 'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize', + function ($rootScope, $location, $log, $routeParams, $compile, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, + GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty, Wait, + GetChoices, UpdateGroup, SourceChange, Find, WatchInventoryWindowResize, ParseVariableString, ToJSON, GroupsScheduleListInit, + SourceForm, SetSchedulesInnerDialogSize) { + 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 = ''; + 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/'; - } + 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(); + $('#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); + elem = document.getElementById('group-modal-dialog'); + $compile(elem)(modal_scope); - generator.inject(GroupForm, { mode: 'edit', id: 'properties-tab', breadCrumbs: false, related: false, scope: properties_scope }); - generator.inject(SourceForm, { mode: 'edit', id: 'sources-tab', breadCrumbs: false, related: false, scope: sources_scope }); + generator.inject(GroupForm, { mode: 'edit', id: 'properties-tab', breadCrumbs: false, related: false, scope: properties_scope }); + generator.inject(SourceForm, { mode: 'edit', id: 'sources-tab', breadCrumbs: false, related: false, scope: sources_scope }); - //generator.reset(); + //generator.reset(); - GetSourceTypeOptions({ scope: sources_scope, variable: 'source_type_options' }); - sources_scope.source = SourceForm.fields.source['default']; - sources_scope.sourcePathRequired = false; - sources_scope[SourceForm.fields.source_vars.parseTypeName] = 'yaml'; - sources_scope.update_cache_timeout = 0; - properties_scope.parseType = 'yaml'; + GetSourceTypeOptions({ scope: sources_scope, variable: 'source_type_options' }); + sources_scope.source = SourceForm.fields.source['default']; + sources_scope.sourcePathRequired = false; + sources_scope[SourceForm.fields.source_vars.parseTypeName] = 'yaml'; + sources_scope.update_cache_timeout = 0; + properties_scope.parseType = 'yaml'; - function waitStop() { Wait('stop'); } + 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 }); - } + // 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 }); + } - // 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; - } + // 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: { - 'Cancel': function() { - modal_scope.cancelModal(); - }, - 'Save': function () { - modal_scope.saveGroup(); - } - }, - 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) }); + // Create the modal + $('#group-modal-dialog').dialog({ + buttons: { + 'Cancel': function() { + modal_scope.cancelModal(); + }, + 'Save': function () { + modal_scope.saveGroup(); + } + }, + 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 () { - $('#group_name').focus(); - Wait('stop'); - } - }); + 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 () { + $('#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: SourceForm.fields.source_vars.parseTypeName, - field_id: 'source_source_vars', onReady: waitStop }); - } else if (sources_scope.source && (sources_scope.source.value === 'vmware')) { - Wait('start'); - ParseTypeChange({ scope: sources_scope, variable: 'inventory_variables', parse_variable: SourceForm.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: SourceForm.fields.extra_vars.parseTypeName, - field_id: 'source_extra_vars', onReady: waitStop }); - } - } - else if ($(e.target).text() === 'Schedule') { - $('#schedules-overlay').hide(); - } - }); + $('#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: SourceForm.fields.source_vars.parseTypeName, + field_id: 'source_source_vars', onReady: waitStop }); + } else if (sources_scope.source && (sources_scope.source.value === 'vmware')) { + Wait('start'); + ParseTypeChange({ scope: sources_scope, variable: 'inventory_variables', parse_variable: SourceForm.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: SourceForm.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 () { - modal_scope.showSourceTab = (mode === 'edit' && group.has_inventory_sources && Empty(group.summary_fields.inventory_source.source) && sources_scope.source.value!=='manual') ? false : 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); - }); + if (modal_scope.groupVariablesLoadedRemove) { + modal_scope.groupVariablesLoadedRemove(); + } + modal_scope.groupVariablesLoadedRemove = modal_scope.$on('groupVariablesLoaded', function () { + modal_scope.showSourceTab = (mode === 'edit' && group.has_inventory_sources && Empty(group.summary_fields.inventory_source.source) && sources_scope.source.value!=='manual') ? false : 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); + }); - // After the group record is loaded, retrieve related data. - // jt-- i'm changing this to act sequentially: first load properties, then sources, then schedule - // I accomplished this by adding "LoadSourceData" which will run the source retrieval code after the property - // variables are set. - if (modal_scope.groupLoadedRemove) { - modal_scope.groupLoadedRemove(); - } - modal_scope.groupLoadedRemove = modal_scope.$on('groupLoaded', function () { - if (properties_scope.variable_url) { - // get group variables - Rest.setUrl(properties_scope.variable_url); - Rest.get() - .success(function (data) { - properties_scope.variables = ParseVariableString(data); - master.variables = properties_scope.variables; - modal_scope.$emit('LoadSourceData'); - //modal_scope.$emit('groupVariablesLoaded'); jt- this needs to get called after sources are loaded - }) - .error(function (data, status) { - properties_scope.variables = null; - ProcessErrors(modal_scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve group variables. GET returned status: ' + status }); - }); - } else { - properties_scope.variables = "---"; - master.variables = properties_scope.variables; - modal_scope.$emit('LoadSourceData'); - //properties_scope.$emit('groupVariablesLoaded'); - } - }); + // After the group record is loaded, retrieve related data. + // jt-- i'm changing this to act sequentially: first load properties, then sources, then schedule + // I accomplished this by adding "LoadSourceData" which will run the source retrieval code after the property + // variables are set. + if (modal_scope.groupLoadedRemove) { + modal_scope.groupLoadedRemove(); + } + modal_scope.groupLoadedRemove = modal_scope.$on('groupLoaded', function () { + if (properties_scope.variable_url) { + // get group variables + Rest.setUrl(properties_scope.variable_url); + Rest.get() + .success(function (data) { + properties_scope.variables = ParseVariableString(data); + master.variables = properties_scope.variables; + modal_scope.$emit('LoadSourceData'); + //modal_scope.$emit('groupVariablesLoaded'); jt- this needs to get called after sources are loaded + }) + .error(function (data, status) { + properties_scope.variables = null; + ProcessErrors(modal_scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve group variables. GET returned status: ' + status }); + }); + } else { + properties_scope.variables = "---"; + master.variables = properties_scope.variables; + modal_scope.$emit('LoadSourceData'); + //properties_scope.$emit('groupVariablesLoaded'); + } + }); - // 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 = SourceForm; - 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 === "source_script"){ - // sources_scope[fld] = data - // } + // 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 = SourceForm; + 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 === "source_script"){ + // sources_scope[fld] = data + // } - else if (data[fld] !== undefined) { - sources_scope[fld] = data[fld]; - 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]; - } - } + 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]; + } + } - sources_scope.sourceChange(); //set defaults that rely on source value + sources_scope.sourceChange(); //set defaults that rely on source value - if (data.source_regions) { - if (data.source === 'ec2' || data.source === 'rax') { - set = (data.source === 'ec2') ? sources_scope.ec2_regions : sources_scope.rax_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; - $('#s2id_source_source_regions').select2('data', opts); - } - } else { - // If empty, default to all - master.source_regions = [{ - id: 'all', - text: 'All' - }]; - $('#s2id_source_source_regions').select2('data', master.source_regions); - } - 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; - $('#s2id_source_group_by').select2('data', 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 (data.source_regions) { + if (data.source === 'ec2' || data.source === 'rax') { + set = (data.source === 'ec2') ? sources_scope.ec2_regions : sources_scope.rax_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; + $('#s2id_source_source_regions').select2('data', opts); + } + } else { + // If empty, default to all + master.source_regions = [{ + id: 'all', + text: 'All' + }]; + $('#s2id_source_source_regions').select2('data', master.source_regions); + } + 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; + $('#s2id_source_group_by').select2('data', 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 (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('groupLoaded'); - }) - .error(function (data, status) { - ProcessErrors(modal_scope, data, status, { hdr: 'Error!', - msg: 'Failed to retrieve group: ' + defaultUrl + '. GET status: ' + status }); - }); - }); + 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('groupLoaded'); + }) + .error(function (data, status) { + ProcessErrors(modal_scope, data, status, { hdr: 'Error!', + msg: 'Failed to retrieve group: ' + defaultUrl + '. GET status: ' + status }); + }); + }); - choicesReady = 0; + choicesReady = 0; - if (sources_scope.removeChoicesReady) { - sources_scope.removeChoicesReady(); - } - sources_scope.removeChoicesReady = sources_scope.$on('choicesReadyGroup', function () { - choicesReady++; - if (choicesReady === 2) { - if (mode === 'edit') { - modal_scope.$emit('choicesCompleteGroup'); - } - else { - properties_scope.variables = "---"; - master.variables = properties_scope.variables; - modal_scope.$emit('groupVariablesLoaded'); - } - } - }); + if (sources_scope.removeChoicesReady) { + sources_scope.removeChoicesReady(); + } + sources_scope.removeChoicesReady = sources_scope.$on('choicesReadyGroup', function () { + 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' - }); + // 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: '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: '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' - }); + 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' - }); + // 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'); + Wait('start'); - if (parent_scope.removeAddTreeRefreshed) { - parent_scope.removeAddTreeRefreshed(); - } - parent_scope.removeAddTreeRefreshed = parent_scope.$on('GroupTreeRefreshed', function() { - // Clean up - Wait('stop'); - WatchInventoryWindowResize(); - if (modal_scope.searchCleanUp) { - modal_scope.searchCleanup(); - } - try { - $('#group-modal-dialog').dialog('close'); - } - catch(e) { - // ignore - } - }); + if (parent_scope.removeAddTreeRefreshed) { + parent_scope.removeAddTreeRefreshed(); + } + parent_scope.removeAddTreeRefreshed = parent_scope.$on('GroupTreeRefreshed', function() { + // Clean up + Wait('stop'); + WatchInventoryWindowResize(); + 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.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 () { + 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. + // 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.source_script, - update_on_launch: sources_scope.update_on_launch, - update_cache_timeout: (sources_scope.update_cache_timeout || 0) - }; + 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.source_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 - regions = $('#s2id_source_source_regions').select2("data"); - r = []; - for (i = 0; i < regions.length; i++) { - r.push(regions[i].id); - } - data.source_regions = r.join(); + // Create a string out of selected list of regions + regions = $('#s2id_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 = $('#s2id_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')) { + data.instance_filters = sources_scope.instance_filters; + // Create a string out of selected list of regions + group_by = $('#s2id_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 === '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 === 'custom')) { + data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.extra_vars, true); + } - if (sources_scope.source && (sources_scope.source.value === 'vmware')) { - data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true); - } + if (sources_scope.source && (sources_scope.source.value === 'vmware')) { + 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); - } + // 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, SourceForm, { hdr: 'Error!', - msg: 'Failed to update group inventory source. PUT status: ' + status }); - }); - } - }); + 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, SourceForm, { hdr: 'Error!', + msg: 'Failed to update group inventory source. PUT status: ' + status }); + }); + } + }); - if (modal_scope.removeUpdateVariables) { - modal_scope.removeUpdateVariables(); - } - modal_scope.removeUpdateVariables = modal_scope.$on('updateVariables', function(e, data, url) { - Rest.setUrl(url); - Rest.put(data) - .success(function () { - modal_scope.$emit('formSaveSuccess'); - }) - .error(function (data, status) { - $('#group_tabs a:eq(0)').tab('show'); - ProcessErrors(modal_scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to update group variables. PUT status: ' + status }); - }); - }); + if (modal_scope.removeUpdateVariables) { + modal_scope.removeUpdateVariables(); + } + modal_scope.removeUpdateVariables = modal_scope.$on('updateVariables', function(e, data, url) { + Rest.setUrl(url); + Rest.put(data) + .success(function () { + modal_scope.$emit('formSaveSuccess'); + }) + .error(function (data, status) { + $('#group_tabs a:eq(0)').tab('show'); + ProcessErrors(modal_scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to update group variables. PUT status: ' + status }); + }); + }); - // Cancel - modal_scope.cancelModal = function () { - try { - $('#group-modal-dialog').dialog('close'); - } - catch(e) { - //ignore - } - if (modal_scope.searchCleanup) { - modal_scope.searchCleanup(); - } - if (parent_scope.restoreSearch) { - parent_scope.restoreSearch(); - } - else { - Wait('stop'); - } - }; + // Cancel + modal_scope.cancelModal = function () { + try { + $('#group-modal-dialog').dialog('close'); + } + catch(e) { + //ignore + } + if (modal_scope.searchCleanup) { + modal_scope.searchCleanup(); + } + if (parent_scope.restoreSearch) { + parent_scope.restoreSearch(); + } + else { + Wait('stop'); + } + }; - // Save - modal_scope.saveGroup = function () { - Wait('start'); - var fld, data, json_data; + // Save + modal_scope.saveGroup = function () { + Wait('start'); + var fld, data, json_data; - try { + try { - json_data = ToJSON(properties_scope.parseType, properties_scope.variables); + json_data = ToJSON(properties_scope.parseType, properties_scope.variables); - data = {}; - for (fld in GroupForm.fields) { - if (fld !== 'variables') { - data[fld] = properties_scope[fld]; - } - } + data = {}; + for (fld in GroupForm.fields) { + if (fld !== 'variables') { + data[fld] = properties_scope[fld]; + } + } - data.inventory = inventory_id; + data.inventory = inventory_id; - Rest.setUrl(defaultUrl); - if (mode === 'edit' || (mode === 'add' && group_created)) { - Rest.put(data) - .success(function () { - if (properties_scope.variables) { - modal_scope.$emit('updateVariables', json_data, properties_scope.variable_url); - } - else { - 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; - if (properties_scope.variables) { - modal_scope.$emit('updateVariables', json_data, data.related.variable_data); - } - else { - 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 - } - }; + Rest.setUrl(defaultUrl); + if (mode === 'edit' || (mode === 'add' && group_created)) { + Rest.put(data) + .success(function () { + if (properties_scope.variables) { + modal_scope.$emit('updateVariables', json_data, properties_scope.variable_url); + } + else { + 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; + if (properties_scope.variables) { + modal_scope.$emit('updateVariables', json_data, data.related.variable_data); + } + else { + 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 ' + - sources_scope.summary_fields.group.name + '. Use the Refresh button to monitor the status.', 'alert-info'); - } 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 - }); - } - }; + // 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 ' + + sources_scope.summary_fields.group.name + '. Use the Refresh button to monitor the status.', 'alert-info'); + } 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 () { - parent_scope.showSchedulesTab = (mode === 'edit' && sources_scope.source && sources_scope.source.value!=="manual") ? true : false; - SourceChange({ scope: sources_scope, form: SourceForm }); - }; + // Change the lookup and regions when the source changes + sources_scope.sourceChange = function () { + parent_scope.showSchedulesTab = (mode === 'edit' && sources_scope.source && sources_scope.source.value!=="manual") ? true : false; + SourceChange({ scope: sources_scope, form: SourceForm }); + }; - }; - } + }; + } ]) /** @@ -1420,237 +1422,237 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GroupsDelete', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', -'Prompt', 'ProcessErrors', 'GetBasePath', 'Wait', 'BuildTree', 'Find', 'CreateDialog', - function ($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, Wait, BuildTree, Find, CreateDialog) { - return function (params) { + 'Prompt', 'ProcessErrors', 'GetBasePath', 'Wait', 'BuildTree', 'Find', 'CreateDialog', + function ($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, + GetBasePath, Wait, BuildTree, Find, CreateDialog) { + return function (params) { - var scope = params.scope, - group_id = params.group_id, - node = Find({ list: scope.groups, key: 'id', val: group_id }), - hosts = [], - groups = [], - childCount = 0, - buttonSet; + var scope = params.scope, + group_id = params.group_id, + node = Find({ list: scope.groups, key: 'id', val: group_id }), + hosts = [], + groups = [], + childCount = 0, + buttonSet; - scope.deleteOption = "preserve-all"; + scope.deleteOption = "preserve-all"; - scope.helpText = "
Delete
Deletes groups and hosts associated with the group being deleted. " + - "If a group or host is associated with other groups, it will still exist within those groups. Otherwise, " + - "the associated groups and hosts will no longer appear in the inventory.
\n" + - "
Promote
Groups and hosts associated with the group being removed will be " + - "promoted one level. Note: groups already associated with other groups cannot be promoted to the top level of the " + - "tree.
\n"; - buttonSet = [{ - label: "Cancel", - onClick: function() { - scope.cancel(); - }, - icon: "fa-times", - "class": "btn btn-default", - "id": "group-delete-cancel-button" - },{ - label: "Delete", - onClick: function() { - scope.performDelete(); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "group-delete-ok-button" - }]; + scope.helpText = "
Delete
Deletes groups and hosts associated with the group being deleted. " + + "If a group or host is associated with other groups, it will still exist within those groups. Otherwise, " + + "the associated groups and hosts will no longer appear in the inventory.
\n" + + "
Promote
Groups and hosts associated with the group being removed will be " + + "promoted one level. Note: groups already associated with other groups cannot be promoted to the top level of the " + + "tree.
\n"; + buttonSet = [{ + label: "Cancel", + onClick: function() { + scope.cancel(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "group-delete-cancel-button" + },{ + label: "Delete", + onClick: function() { + scope.performDelete(); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "group-delete-ok-button" + }]; - if (scope.removeDeleteDialogReady) { - scope.removeDeleteDialogReady(); - } + if (scope.removeDeleteDialogReady) { + scope.removeDeleteDialogReady(); + } - scope.removeDeleteDialogReady = scope.$on('DeleteDialogReady', function() { - Wait('stop'); - $('#group-delete-dialog').dialog('open'); - }); + scope.removeDeleteDialogReady = scope.$on('DeleteDialogReady', function() { + Wait('stop'); + $('#group-delete-dialog').dialog('open'); + }); - if (scope.removeShowDeleteDialog) { - scope.removeShowDeleteDialog(); - } + if (scope.removeShowDeleteDialog) { + scope.removeShowDeleteDialog(); + } - scope.removeShowDeleteDialog = scope.$on('ShowDeleteDialog', function() { - scope.group_name = node.name; - scope.groupsCount = groups.length; - scope.hostsCount = hosts.length; - CreateDialog({ - id: 'group-delete-dialog', - scope: scope, - buttons: buttonSet, - width: 650, - height: 350, - minWidth: 500, - title: 'Delete Group', - callback: 'DeleteDialogReady' - }); - }); + scope.removeShowDeleteDialog = scope.$on('ShowDeleteDialog', function() { + scope.group_name = node.name; + scope.groupsCount = groups.length; + scope.hostsCount = hosts.length; + CreateDialog({ + id: 'group-delete-dialog', + scope: scope, + buttons: buttonSet, + width: 650, + height: 350, + minWidth: 500, + title: 'Delete Group', + callback: 'DeleteDialogReady' + }); + }); - if (scope.removeChildrenReady) { - scope.removeChildrenReady(); - } + if (scope.removeChildrenReady) { + scope.removeChildrenReady(); + } - scope.removeChildrenReady = scope.$on('ChildrenReady', function() { - childCount++; - if (childCount === 2) { - scope.$emit('ShowDeleteDialog'); - } - }); + scope.removeChildrenReady = scope.$on('ChildrenReady', function() { + childCount++; + if (childCount === 2) { + scope.$emit('ShowDeleteDialog'); + } + }); - Wait('start'); + Wait('start'); - // this function is used to make sure that we get all of the groups/hosts, - // not just the first page - scope.next_iterator = function(next_url, type) { - Rest.setUrl(next_url); - Rest.get() - .success(function(data) { - if (data.count) { - if (type === "groups") { - data.results.forEach(function(group) { - groups.push(group); - }); - } - else if (type === "hosts") { - data.results.forEach(function(host) { - hosts.push(host); - }); - } - } + // this function is used to make sure that we get all of the groups/hosts, + // not just the first page + scope.next_iterator = function(next_url, type) { + Rest.setUrl(next_url); + Rest.get() + .success(function(data) { + if (data.count) { + if (type === "groups") { + data.results.forEach(function(group) { + groups.push(group); + }); + } + else if (type === "hosts") { + data.results.forEach(function(host) { + hosts.push(host); + }); + } + } - if (data.next) { - if (type === "groups") { - scope.next_iterator(data.next, "groups"); - } else if (type === "hosts") { - scope.next_iterator(data.next, "hosts"); - } - } - else { - scope.$emit('ChildrenReady'); - } - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve related groups. GET returned: ' + status - }); - }); - }; + if (data.next) { + if (type === "groups") { + scope.next_iterator(data.next, "groups"); + } else if (type === "hosts") { + scope.next_iterator(data.next, "hosts"); + } + } + else { + scope.$emit('ChildrenReady'); + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve related groups. GET returned: ' + status + }); + }); + }; - if (node.related.children) { - Rest.setUrl(node.related.children); - Rest.get() - .success(function(data) { - if (data.count) { - data.results.forEach(function(group) { - groups.push(group); - }); - } + if (node.related.children) { + Rest.setUrl(node.related.children); + Rest.get() + .success(function(data) { + if (data.count) { + data.results.forEach(function(group) { + groups.push(group); + }); + } - if (data.next) { - scope.next_iterator(data.next, "groups"); - } else { - scope.$emit('ChildrenReady'); - } - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve related groups. GET returned: ' + status - }); - }); - } - else { - scope.$emit('ChildrenReady'); - } + if (data.next) { + scope.next_iterator(data.next, "groups"); + } else { + scope.$emit('ChildrenReady'); + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve related groups. GET returned: ' + status + }); + }); + } + else { + scope.$emit('ChildrenReady'); + } - if (node.related.all_hosts) { - Rest.setUrl(node.related.all_hosts); - Rest.get() - .success( function(data) { - if (data.count) { - data.results.forEach(function(host) { - hosts.push(host); - }); - } - if (data.next) { - scope.next_iterator(data.next, "hosts"); - } else { - scope.$emit('ChildrenReady'); - } - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve related hosts. GET returned: ' + status - }); - }); - } - else { - scope.$emit('ChildrenReady'); - } + if (node.related.all_hosts) { + Rest.setUrl(node.related.all_hosts); + Rest.get() + .success( function(data) { + if (data.count) { + data.results.forEach(function(host) { + hosts.push(host); + }); + } + if (data.next) { + scope.next_iterator(data.next, "hosts"); + } else { + scope.$emit('ChildrenReady'); + } + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve related hosts. GET returned: ' + status + }); + }); + } + else { + scope.$emit('ChildrenReady'); + } - if (scope.removeDisassociateGroup) { - scope.removeDisassociateGroup(); - } + if (scope.removeDisassociateGroup) { + scope.removeDisassociateGroup(); + } - scope.removeDisassociateGroup = scope.$on('DisassociateGroup', function() { - var data, url; - if (!scope.selected_group_id) { - url = GetBasePath('inventory') + scope.inventory.id + '/groups/'; - data = { id: node.id, disassociate: 1 }; - } - else { - url = GetBasePath('groups') + node.id + '/children/'; - data = { disassociate: 1 }; - } + scope.removeDisassociateGroup = scope.$on('DisassociateGroup', function() { + var data, url; + if (!scope.selected_group_id) { + url = GetBasePath('inventory') + scope.inventory.id + '/groups/'; + data = { id: node.id, disassociate: 1 }; + } + else { + url = GetBasePath('groups') + node.id + '/children/'; + data = { disassociate: 1 }; + } - Rest.setUrl(url); - Rest.post(data) - .success(function () { - scope.$emit('GroupDeleteCompleted'); // Signal a group refresh to start - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. POST returned: ' + status - }); - }); - }); + Rest.setUrl(url); + Rest.post(data) + .success(function () { + scope.$emit('GroupDeleteCompleted'); // Signal a group refresh to start + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. POST returned: ' + status + }); + }); + }); - if (scope.removeDeleteGroup) { - scope.removeDeleteGroup(); - } + if (scope.removeDeleteGroup) { + scope.removeDeleteGroup(); + } - scope.removeDeleteGroup = scope.$on('DeleteGroup', function() { - var url = GetBasePath('groups') + node.id + '/'; - Rest.setUrl(url); - Rest.destroy() - .success( function() { - scope.$emit('GroupDeleteCompleted'); // Signal a group refresh to start - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned: ' + status - }); - }); - }); + scope.removeDeleteGroup = scope.$on('DeleteGroup', function() { + var url = GetBasePath('groups') + node.id + '/'; + Rest.setUrl(url); + Rest.destroy() + .success( function() { + scope.$emit('GroupDeleteCompleted'); // Signal a group refresh to start + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned: ' + status + }); + }); + }); - scope.cancel = function() { - $('#group-delete-dialog').dialog('close'); - }; + scope.cancel = function() { + $('#group-delete-dialog').dialog('close'); + }; - scope.performDelete = function() { - $('#group-delete-dialog').dialog('close'); - Wait('start'); - if (scope.deleteOption === 'delete-all' || (scope.groupsCount === 0 && scope.hostsCount === 0)) { - // If user chooses Delete or there are no children, send DELETE request - scope.$emit('DeleteGroup'); - } - else { - scope.$emit('DisassociateGroup'); - } - }; - }; - } + scope.performDelete = function() { + $('#group-delete-dialog').dialog('close'); + Wait('start'); + if (scope.deleteOption === 'delete-all' || (scope.groupsCount === 0 && scope.hostsCount === 0)) { + // If user chooses Delete or there are no children, send DELETE request + scope.$emit('DeleteGroup'); + } + else { + scope.$emit('DisassociateGroup'); + } + }; + }; + } ]) /** @@ -1659,26 +1661,26 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GetRootGroups', ['Rest', 'ProcessErrors', 'GetBasePath', - function(Rest, ProcessErrors, GetBasePath) { - return function(params) { - var scope = params.scope, - inventory_id = params.inventory_id, - //group_id = params.group_id, - callback = params.callback, - url; + function(Rest, ProcessErrors, GetBasePath) { + return function(params) { + var scope = params.scope, + inventory_id = params.inventory_id, + //group_id = params.group_id, + callback = params.callback, + url; - url = GetBasePath('inventory') + inventory_id + '/root_groups/'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - scope.$emit(callback, data.results); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status }); - }); - }; - } + url = GetBasePath('inventory') + inventory_id + '/root_groups/'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + scope.$emit(callback, data.results); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET returned: ' + status }); + }); + }; + } ]) /** @@ -1687,301 +1689,301 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('GroupsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'GenerateList', 'GroupList', 'SearchInit', -'PaginateInit', 'GetRootGroups', - function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit, GetRootGroups) { - return function(params) { + 'PaginateInit', 'GetRootGroups', + function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit, GetRootGroups) { + return function(params) { - var group_id = params.group_id, - parent_scope = params.scope, - scope = parent_scope.$new(), - parent_group = parent_scope.selected_group_id, - buttonSet, url, group; + var group_id = params.group_id, + parent_scope = params.scope, + scope = parent_scope.$new(), + parent_group = parent_scope.selected_group_id, + buttonSet, url, group; - buttonSet = [{ - label: "Cancel", - onClick: function() { - scope.cancel(); - }, - icon: "fa-times", - "class": "btn btn-default", - "id": "group-copy-cancel-button" - },{ - label: "OK", - onClick: function() { - scope.performCopy(); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "group-copy-ok-button" - }]; + buttonSet = [{ + label: "Cancel", + onClick: function() { + scope.cancel(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "group-copy-cancel-button" + },{ + label: "OK", + onClick: function() { + scope.performCopy(); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "group-copy-ok-button" + }]; - if (scope.removeGroupsCopyPostRefresh) { - scope.removeGroupsCopyPostRefresh(); - } - scope.removeGroupCopyPostRefresh = scope.$on('PostRefresh', function() { - scope.copy_groups.forEach(function(row, i) { - scope.copy_groups[i].checked = '0'; - }); - Wait('stop'); - $('#group-copy-dialog').dialog('open'); - $('#group-copy-ok-button').attr('disabled','disabled'); + if (scope.removeGroupsCopyPostRefresh) { + scope.removeGroupsCopyPostRefresh(); + } + scope.removeGroupCopyPostRefresh = scope.$on('PostRefresh', function() { + scope.copy_groups.forEach(function(row, i) { + scope.copy_groups[i].checked = '0'; + }); + Wait('stop'); + $('#group-copy-dialog').dialog('open'); + $('#group-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(); - } - }); + // 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.removeCopyDialogReady) { - scope.removeCopyDialogReady(); - } - scope.removeCopyDialogReady = scope.$on('CopyDialogReady', function() { - var url = GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; - url += (parent_group) ? '?not__id__in=' + group_id + ',' + parent_group : '?not__id=' + group_id; - GenerateList.inject(GroupList, { - mode: 'lookup', - id: 'copy-select-container', - scope: scope - //, - //instructions: instructions - }); - SearchInit({ - scope: scope, - set: GroupList.name, - list: GroupList, - url: url - }); - PaginateInit({ - scope: scope, - list: GroupList, - url: url, - mode: 'lookup' - }); - scope.search(GroupList.iterator); - }); + if (scope.removeCopyDialogReady) { + scope.removeCopyDialogReady(); + } + scope.removeCopyDialogReady = scope.$on('CopyDialogReady', function() { + var url = GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; + url += (parent_group) ? '?not__id__in=' + group_id + ',' + parent_group : '?not__id=' + group_id; + GenerateList.inject(GroupList, { + mode: 'lookup', + id: 'copy-select-container', + scope: scope + //, + //instructions: instructions + }); + SearchInit({ + scope: scope, + set: GroupList.name, + list: GroupList, + url: url + }); + PaginateInit({ + scope: scope, + list: GroupList, + url: url, + mode: 'lookup' + }); + scope.search(GroupList.iterator); + }); - if (scope.removeShowDialog) { - scope.removeShowDialog(); - } - scope.removeShowDialog = scope.$on('ShowDialog', function() { - var d; - scope.name = group.name; - scope.copy_choice = "copy"; - d = angular.element(document.getElementById('group-copy-dialog')); - $compile(d)(scope); + if (scope.removeShowDialog) { + scope.removeShowDialog(); + } + scope.removeShowDialog = scope.$on('ShowDialog', function() { + var d; + scope.name = group.name; + scope.copy_choice = "copy"; + d = angular.element(document.getElementById('group-copy-dialog')); + $compile(d)(scope); - CreateDialog({ - id: 'group-copy-dialog', - scope: scope, - buttons: buttonSet, - width: 650, - height: 650, - minWidth: 600, - title: 'Copy or Move Group', - callback: 'CopyDialogReady', - onClose: function() { - scope.cancel(); - } - }); - }); + CreateDialog({ + id: 'group-copy-dialog', + scope: scope, + buttons: buttonSet, + width: 650, + height: 650, + minWidth: 600, + title: 'Copy or Move Group', + callback: 'CopyDialogReady', + onClose: function() { + scope.cancel(); + } + }); + }); - if (scope.removeRootGroupsReady) { - scope.removeRootGroupsReady(); - } - scope.removeRootGroupsReady = scope.$on('RootGroupsReady', function(e, root_groups) { - scope.offer_root_group = true; - scope.use_root_group = false; - root_groups.every(function(row) { - if (row.id === group_id) { - scope.offer_root_group = false; - return false; - } - return true; - }); - url = GetBasePath('groups') + group_id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - group = data; - scope.$emit('ShowDialog'); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status }); - }); - }); + if (scope.removeRootGroupsReady) { + scope.removeRootGroupsReady(); + } + scope.removeRootGroupsReady = scope.$on('RootGroupsReady', function(e, root_groups) { + scope.offer_root_group = true; + scope.use_root_group = false; + root_groups.every(function(row) { + if (row.id === group_id) { + scope.offer_root_group = false; + return false; + } + return true; + }); + url = GetBasePath('groups') + group_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + group = data; + scope.$emit('ShowDialog'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET returned: ' + status }); + }); + }); - Wait('start'); + Wait('start'); - GetRootGroups({ - scope: scope, - group_id: group_id, - inventory_id: parent_scope.inventory.id, - callback: 'RootGroupsReady' - }); + GetRootGroups({ + scope: scope, + group_id: group_id, + inventory_id: parent_scope.inventory.id, + callback: 'RootGroupsReady' + }); - scope.cancel = function() { - $(document).off("keydown"); - try { - $('#group-copy-dialog').dialog('close'); - } - catch(e) { - // ignore - } - scope.searchCleanup(); - parent_scope.restoreSearch(); - scope.$destroy(); - }; + scope.cancel = function() { + $(document).off("keydown"); + try { + $('#group-copy-dialog').dialog('close'); + } + catch(e) { + // ignore + } + scope.searchCleanup(); + parent_scope.restoreSearch(); + 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 === '0') { - 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 = ''; - } - } 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 === '1') { - count++; - } - }); - if (count === 0) { - $('#group-copy-ok-button').attr('disabled','disabled'); - } - else { - $('#group-copy-ok-button').removeAttr('disabled'); - } - }; + 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 === '0') { + 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 = ''; + } + } 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 === '1') { + count++; + } + }); + if (count === 0) { + $('#group-copy-ok-button').attr('disabled','disabled'); + } + else { + $('#group-copy-ok-button').removeAttr('disabled'); + } + }; - scope.toggleUseRootGroup = function() { - var list = GroupList; - //console.log("scope.use_root_group: " + scope.use_root_group); - if (scope.use_root_group) { - $('#group-copy-ok-button').removeAttr('disabled'); - } - else { - // check for group selection - $('#group-copy-ok-button').attr('disabled','disabled'); - scope[list.name].every(function(row) { - if (row.checked === '1') { - $('#group-copy-ok-button').removeAttr('disabled'); - return false; - } - return true; - }); - } - }; + scope.toggleUseRootGroup = function() { + var list = GroupList; + //console.log("scope.use_root_group: " + scope.use_root_group); + if (scope.use_root_group) { + $('#group-copy-ok-button').removeAttr('disabled'); + } + else { + // check for group selection + $('#group-copy-ok-button').attr('disabled','disabled'); + scope[list.name].every(function(row) { + if (row.checked === '1') { + $('#group-copy-ok-button').removeAttr('disabled'); + return false; + } + return true; + }); + } + }; - scope.performCopy = function() { - var list = GroupList, - target, - url; + scope.performCopy = function() { + var list = GroupList, + target, + url; - Wait('start'); + 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.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 + if (scope.copy_choice === 'move') { + // Respond to move - // disassociate the group from the original parent - if (scope.removeGroupRemove) { - scope.removeGroupRemove(); - } - scope.removeGroupRemove = scope.$on('RemoveGroup', function () { - if (parent_group > 0) { - // Only remove a group from a parent when the parent is a group and not the inventory root - url = GetBasePath('groups') + parent_group + '/children/'; - Rest.setUrl(url); - Rest.post({ id: group.id, disassociate: 1 }) - .success(function () { - scope.cancel(); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to remove ' + group.name + ' from group ' + parent_group + '. POST returned: ' + status }); - }); - } else { - scope.cancel(); - } - }); + // disassociate the group from the original parent + if (scope.removeGroupRemove) { + scope.removeGroupRemove(); + } + scope.removeGroupRemove = scope.$on('RemoveGroup', function () { + if (parent_group > 0) { + // Only remove a group from a parent when the parent is a group and not the inventory root + url = GetBasePath('groups') + parent_group + '/children/'; + Rest.setUrl(url); + Rest.post({ id: group.id, disassociate: 1 }) + .success(function () { + scope.cancel(); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to remove ' + group.name + ' from group ' + parent_group + '. POST returned: ' + status }); + }); + } else { + scope.cancel(); + } + }); - // add the new group to the target - url = (target) ? - GetBasePath('groups') + target.id + '/children/' : - GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; - group = { - id: group.id, - name: group.name, - description: group.description, - inventory: parent_scope.inventory.id - }; - Rest.setUrl(url); - Rest.post(group) - .success(function () { - scope.$emit('RemoveGroup'); - }) - .error(function (data, status) { - var target_name = (target) ? target.name : 'inventory'; - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status }); - }); - } - else { - // Respond to copy by adding the new group to the target - url = (target) ? - GetBasePath('groups') + target.id + '/children/' : - GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; + // add the new group to the target + url = (target) ? + GetBasePath('groups') + target.id + '/children/' : + GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; + group = { + id: group.id, + name: group.name, + description: group.description, + inventory: parent_scope.inventory.id + }; + Rest.setUrl(url); + Rest.post(group) + .success(function () { + scope.$emit('RemoveGroup'); + }) + .error(function (data, status) { + var target_name = (target) ? target.name : 'inventory'; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status }); + }); + } + else { + // Respond to copy by adding the new group to the target + url = (target) ? + GetBasePath('groups') + target.id + '/children/' : + GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; - group = { - id: group.id, - name: group.name, - description: group.description, - inventory: parent_scope.inventory.id - }; + group = { + id: group.id, + name: group.name, + description: group.description, + inventory: parent_scope.inventory.id + }; - Rest.setUrl(url); - Rest.post(group) - .success(function () { - scope.cancel(); - }) - .error(function (data, status) { - var target_name = (target) ? target.name : 'inventory'; - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status - }); - }); - } - }; + Rest.setUrl(url); + Rest.post(group) + .success(function () { + scope.cancel(); + }) + .error(function (data, status) { + var target_name = (target) ? target.name : 'inventory'; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status + }); + }); + } + }; - }; - } + }; + } ]) /** @@ -1990,134 +1992,134 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' * */ .factory('ShowUpdateStatus', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GenerateForm', -'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventoryStatusForm', 'Wait', - function ($rootScope, $location, $log, $routeParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, - FormatDate, InventoryStatusForm, Wait) { - return function (params) { + 'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventoryStatusForm', 'Wait', + function ($rootScope, $location, $log, $routeParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, + FormatDate, InventoryStatusForm, Wait) { + return function (params) { - var group_name = params.group_name, - last_update = params.last_update, - generator = GenerateForm, - form = InventoryStatusForm, - license_error = params.license_error, - maxrows, html, scope, ww, wh, x, y; + var group_name = params.group_name, + last_update = params.last_update, + generator = GenerateForm, + form = InventoryStatusForm, + license_error = params.license_error, + maxrows, html, scope, ww, wh, x, y; - function calcRows(content) { - var n, rows; - n = content.match(/\n/g); - rows = (n) ? n.length : 1; - return (rows > maxrows) ? maxrows : rows; - } + function calcRows(content) { + var n, rows; + n = content.match(/\n/g); + rows = (n) ? n.length : 1; + return (rows > maxrows) ? maxrows : rows; + } - if (last_update === undefined || last_update === null || last_update === '') { - Wait('stop'); - Alert('Missing Configuration', 'The selected group is not configured for inventory sync. ' + - 'Edit the group and provide Source information.', 'alert-info'); - } else { - html = "
\n" + - "
\n"; + if (last_update === undefined || last_update === null || last_update === '') { + Wait('stop'); + Alert('Missing Configuration', 'The selected group is not configured for inventory sync. ' + + 'Edit the group and provide Source information.', 'alert-info'); + } else { + html = "
\n" + + "
\n"; - $('#inventory-modal-container').empty().append(html); - scope = generator.inject(form, { - mode: 'edit', - id: 'form-container', - breadCrumbs: false, - related: false - }); + $('#inventory-modal-container').empty().append(html); + scope = generator.inject(form, { + mode: 'edit', + id: 'form-container', + breadCrumbs: false, + related: false + }); - // Set modal dimensions based on viewport width - ww = $(document).width(); - wh = $('body').height(); - if (ww > 1199) { - // desktop - x = 675; - y = (750 > wh) ? wh - 20 : 750; - maxrows = 18; - } else if (ww <= 1199 && ww >= 768) { - x = 550; - y = (620 > wh) ? wh - 15 : 620; - maxrows = 12; - } else { - x = (ww - 20); - y = (500 > wh) ? wh : 500; - maxrows = 10; - } + // Set modal dimensions based on viewport width + ww = $(document).width(); + wh = $('body').height(); + if (ww > 1199) { + // desktop + x = 675; + y = (750 > wh) ? wh - 20 : 750; + maxrows = 18; + } else if (ww <= 1199 && ww >= 768) { + x = 550; + y = (620 > wh) ? wh - 15 : 620; + maxrows = 12; + } else { + x = (ww - 20); + y = (500 > wh) ? wh : 500; + maxrows = 10; + } - // Create the modal - $('#status-modal-dialog').dialog({ - buttons: { - "OK": function () { - $(this).dialog("close"); - } - }, - modal: true, - width: x, - height: y, - autoOpen: false, - create: function () { - // fix the close button - $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button') - .empty().attr({ - 'class': 'close' - }).text('x'); - // fix the OK button - $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-buttonset button:first') - .attr({ - 'class': 'btn btn-primary' - }); - }, - 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="status-modal-dialog"]'), - content = dialog.find('#status-modal-dialog'); - content.width(dialog.width() - 28); - }, - close: function () { - // Destroy on close - $('.tooltip').each(function () { - // Remove any lingering tooltip
elements - $(this).remove(); - }); - $('.popover').each(function () { - // remove lingering popover
elements - $(this).remove(); - }); - $('#status-modal-dialog').dialog('destroy'); - $('#inventory-modal-container').empty(); - //WatchInventoryWindowResize(); - }, - open: function () { - Wait('stop'); - } - }); + // Create the modal + $('#status-modal-dialog').dialog({ + buttons: { + "OK": function () { + $(this).dialog("close"); + } + }, + modal: true, + width: x, + height: y, + autoOpen: false, + create: function () { + // fix the close button + $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button') + .empty().attr({ + 'class': 'close' + }).text('x'); + // fix the OK button + $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-buttonset button:first') + .attr({ + 'class': 'btn btn-primary' + }); + }, + 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="status-modal-dialog"]'), + content = dialog.find('#status-modal-dialog'); + content.width(dialog.width() - 28); + }, + close: function () { + // Destroy on close + $('.tooltip').each(function () { + // Remove any lingering tooltip
elements + $(this).remove(); + }); + $('.popover').each(function () { + // remove lingering popover
elements + $(this).remove(); + }); + $('#status-modal-dialog').dialog('destroy'); + $('#inventory-modal-container').empty(); + //WatchInventoryWindowResize(); + }, + open: function () { + Wait('stop'); + } + }); - Rest.setUrl(last_update); - Rest.get() - .success(function (data) { - for (var fld in form.fields) { - if (data[fld]) { - if (fld === 'created') { - scope[fld] = FormatDate(new Date(data[fld])); - } else { - scope[fld] = data[fld]; - } - } - } - scope.license_error = license_error; - scope.status_rows = calcRows(data.status); - scope.stdout_rows = calcRows(data.result_stdout); - scope.traceback_rows = calcRows(data.result_traceback); - $('#status-modal-dialog').dialog('open'); - }) - .error(function (data, status) { - $('#form-modal').modal("hide"); - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', - msg: 'Failed to retrieve last update: ' + last_update + '. GET status: ' + status - }); - }); - } + Rest.setUrl(last_update); + Rest.get() + .success(function (data) { + for (var fld in form.fields) { + if (data[fld]) { + if (fld === 'created') { + scope[fld] = FormatDate(new Date(data[fld])); + } else { + scope[fld] = data[fld]; + } + } + } + scope.license_error = license_error; + scope.status_rows = calcRows(data.status); + scope.stdout_rows = calcRows(data.result_stdout); + scope.traceback_rows = calcRows(data.result_traceback); + $('#status-modal-dialog').dialog('open'); + }) + .error(function (data, status) { + $('#form-modal').modal("hide"); + ProcessErrors(scope, data, status, null, { + hdr: 'Error!', + msg: 'Failed to retrieve last update: ' + last_update + '. GET status: ' + status + }); + }); + } - }; - } + }; + } ]); diff --git a/awx/ui/static/js/helpers/HostEventsViewer.js b/awx/ui/static/js/helpers/HostEventsViewer.js index cbc8152bd9..6e035df035 100644 --- a/awx/ui/static/js/helpers/HostEventsViewer.js +++ b/awx/ui/static/js/helpers/HostEventsViewer.js @@ -12,278 +12,278 @@ * @description view a list of events for a given job and host */ +export default + angular.module('HostEventsViewerHelper', ['ModalDialog', 'Utilities', 'EventViewerHelper']) -angular.module('HostEventsViewerHelper', ['ModalDialog', 'Utilities', 'EventViewerHelper']) + .factory('HostEventsViewer', ['$log', '$compile', 'CreateDialog', 'Wait', 'GetBasePath', 'Empty', 'GetEvents', 'EventViewer', + function($log, $compile, CreateDialog, Wait, GetBasePath, Empty, GetEvents, EventViewer) { + return function(params) { + var parent_scope = params.scope, + scope = parent_scope.$new(true), + job_id = params.job_id, + url = params.url, + title = params.title, //optional + fixHeight, buildTable, + lastID, setStatus, buildRow, status; - .factory('HostEventsViewer', ['$log', '$compile', 'CreateDialog', 'Wait', 'GetBasePath', 'Empty', 'GetEvents', 'EventViewer', - function($log, $compile, CreateDialog, Wait, GetBasePath, Empty, GetEvents, EventViewer) { - return function(params) { - var parent_scope = params.scope, - scope = parent_scope.$new(true), - job_id = params.job_id, - url = params.url, - title = params.title, //optional - fixHeight, buildTable, - lastID, setStatus, buildRow, status; - - // initialize the status dropdown - scope.host_events_status_options = [ - { value: "all", name: "All" }, - { value: "changed", name: "Changed" }, - { value: "failed", name: "Failed" }, - { value: "ok", name: "OK" }, - { value: "unreachable", name: "Unreachable" } - ]; - scope.host_events_search_name = params.name; - status = (params.status) ? params.status : 'all'; - scope.host_events_status_options.every(function(opt, idx) { - if (opt.value === status) { - scope.host_events_search_status = scope.host_events_status_options[idx]; - return false; - } - return true; - }); - if (!scope.host_events_search_status) { - scope.host_events_search_status = scope.host_events_status_options[0]; - } - - $log.debug('job_id: ' + job_id + ' url: ' + url + ' title: ' + title + ' name: ' + name + ' status: ' + status); - - scope.eventsSearchActive = (scope.host_events_search_name) ? true : false; - - if (scope.removeModalReady) { - scope.removeModalReady(); - } - scope.removeModalReady = scope.$on('ModalReady', function() { - scope.hostViewSearching = false; - $('#host-events-modal-dialog').dialog('open'); - }); - - if (scope.removeJobReady) { - scope.removeJobReady(); - } - scope.removeEventReady = scope.$on('EventsReady', function(e, data, maxID) { - var elem, html; - - lastID = maxID; - html = buildTable(data); - $('#host-events').html(html); - elem = angular.element(document.getElementById('host-events-modal-dialog')); - $compile(elem)(scope); - - CreateDialog({ - scope: scope, - width: 675, - height: 600, - minWidth: 450, - callback: 'ModalReady', - id: 'host-events-modal-dialog', - onResizeStop: fixHeight, - title: ( (title) ? title : 'Host Events' ), - onClose: function() { - try { - scope.$destroy(); - } - catch(e) { - //ignore - } - }, - onOpen: function() { - fixHeight(); + // initialize the status dropdown + scope.host_events_status_options = [ + { value: "all", name: "All" }, + { value: "changed", name: "Changed" }, + { value: "failed", name: "Failed" }, + { value: "ok", name: "OK" }, + { value: "unreachable", name: "Unreachable" } + ]; + scope.host_events_search_name = params.name; + status = (params.status) ? params.status : 'all'; + scope.host_events_status_options.every(function(opt, idx) { + if (opt.value === status) { + scope.host_events_search_status = scope.host_events_status_options[idx]; + return false; } + return true; }); - }); - - if (scope.removeRefreshHTML) { - scope.removeRefreshHTML(); - } - scope.removeRefreshHTML = scope.$on('RefreshHTML', function(e, data) { - var elem, html = buildTable(data); - $('#host-events').html(html); - scope.hostViewSearching = false; - elem = angular.element(document.getElementById('host-events')); - $compile(elem)(scope); - }); - - setStatus = function(result) { - var msg = '', status = 'ok', status_text = 'OK'; - if (!result.task && result.event_data && result.event_data.res && result.event_data.res.ansible_facts) { - result.task = "Gathering Facts"; + if (!scope.host_events_search_status) { + scope.host_events_search_status = scope.host_events_status_options[0]; } - if (result.event === "runner_on_no_hosts") { - msg = "No hosts remaining"; - } - if (result.event === 'runner_on_unreachable') { - status = 'unreachable'; - status_text = 'Unreachable'; - } - else if (result.failed) { - status = 'failed'; - status_text = 'Failed'; - } - else if (result.changed) { - status = 'changed'; - status_text = 'Changed'; - } - if (result.event_data.res && result.event_data.res.msg) { - msg = result.event_data.res.msg; - } - result.msg = msg; - result.status = status; - result.status_text = status_text; - return result; - }; - buildRow = function(res) { - var html = ''; - html += "\n"; - html += " " + res.status_text + "\n"; - html += "" + res.host_name + "\n"; - html += "" + res.play + "\n"; - html += "" + res.task + "\n"; - html += ""; - return html; - }; + $log.debug('job_id: ' + job_id + ' url: ' + url + ' title: ' + title + ' name: ' + name + ' status: ' + status); - buildTable = function(data) { - var html = "\n"; - html += "\n"; - data.results.forEach(function(result) { - var res = setStatus(result); - html += buildRow(res); - }); - html += "\n"; - html += "
\n"; - return html; - }; - - fixHeight = function() { - var available_height = $('#host-events-modal-dialog').height() - $('#host-events-modal-dialog #search-form').height() - $('#host-events-modal-dialog #fixed-table-header').height(); - $('#host-events').height(available_height); - $log.debug('set height to: ' + available_height); - // Check width and reset search fields - if ($('#host-events-modal-dialog').width() <= 450) { - $('#host-events-modal-dialog #status-field').css({'margin-left': '7px'}); - } - else { - $('#host-events-modal-dialog #status-field').css({'margin-left': '15px'}); - } - }; - - GetEvents({ - url: url, - scope: scope, - callback: 'EventsReady' - }); - - scope.modalOK = function() { - $('#host-events-modal-dialog').dialog('close'); - scope.$destroy(); - }; - - scope.searchEvents = function() { scope.eventsSearchActive = (scope.host_events_search_name) ? true : false; - GetEvents({ - scope: scope, - url: url, - callback: 'RefreshHTML' - }); - }; - scope.searchEventKeyPress = function(e) { - if (e.keyCode === 13) { - scope.searchEvents(); + if (scope.removeModalReady) { + scope.removeModalReady(); } - }; - - scope.showDetails = function(id) { - EventViewer({ - scope: parent_scope, - url: GetBasePath('jobs') + job_id + '/job_events/?id=' + id, + scope.removeModalReady = scope.$on('ModalReady', function() { + scope.hostViewSearching = false; + $('#host-events-modal-dialog').dialog('open'); }); - }; - if (scope.removeEventsScrollDownBuild) { - scope.removeEventsScrollDownBuild(); - } - scope.removeEventsScrollDownBuild = scope.$on('EventScrollDownBuild', function(e, data, maxID) { - var elem, html = ''; - lastID = maxID; - data.results.forEach(function(result) { - var res = setStatus(result); - html += buildRow(res); + if (scope.removeJobReady) { + scope.removeJobReady(); + } + scope.removeEventReady = scope.$on('EventsReady', function(e, data, maxID) { + var elem, html; + + lastID = maxID; + html = buildTable(data); + $('#host-events').html(html); + elem = angular.element(document.getElementById('host-events-modal-dialog')); + $compile(elem)(scope); + + CreateDialog({ + scope: scope, + width: 675, + height: 600, + minWidth: 450, + callback: 'ModalReady', + id: 'host-events-modal-dialog', + onResizeStop: fixHeight, + title: ( (title) ? title : 'Host Events' ), + onClose: function() { + try { + scope.$destroy(); + } + catch(e) { + //ignore + } + }, + onOpen: function() { + fixHeight(); + } + }); }); - if (html) { - $('#host-events table tbody').append(html); + + if (scope.removeRefreshHTML) { + scope.removeRefreshHTML(); + } + scope.removeRefreshHTML = scope.$on('RefreshHTML', function(e, data) { + var elem, html = buildTable(data); + $('#host-events').html(html); + scope.hostViewSearching = false; elem = angular.element(document.getElementById('host-events')); $compile(elem)(scope); - } - }); - - scope.hostEventsScrollDown = function() { - GetEvents({ - scope: scope, - url: url, - gt: lastID, - callback: 'EventScrollDownBuild' }); - }; - }; - }]) - - .factory('GetEvents', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { - return function(params) { - var url = params.url, - scope = params.scope, - gt = params.gt, - callback = params.callback; - - if (scope.host_events_search_name) { - url += '?host_name=' + scope.host_events_search_name; - } - else { - url += '?host_name__isnull=false'; - } - - if (scope.host_events_search_status.value === 'changed') { - url += '&event__icontains=runner&changed=true'; - } - else if (scope.host_events_search_status.value === 'failed') { - url += '&event__icontains=runner&failed=true'; - } - else if (scope.host_events_search_status.value === 'ok') { - url += '&event=runner_on_ok&changed=false'; - } - else if (scope.host_events_search_status.value === 'unreachable') { - url += '&event=runner_on_unreachable'; - } - else if (scope.host_events_search_status.value === 'all') { - url += '&event__icontains=runner¬__event=runner_on_skipped'; - } - - if (gt) { - // used for endless scroll - url += '&id__gt=' + gt; - } - - url += '&page_size=50&order=id'; - - scope.hostViewSearching = true; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - var lastID; - scope.hostViewSearching = false; - if (data.results.length > 0) { - lastID = data.results[data.results.length - 1].id; + setStatus = function(result) { + var msg = '', status = 'ok', status_text = 'OK'; + if (!result.task && result.event_data && result.event_data.res && result.event_data.res.ansible_facts) { + result.task = "Gathering Facts"; } - scope.$emit(callback, data, lastID); - }) - .error(function(data, status) { - scope.hostViewSearching = false; - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get events ' + url + '. GET returned: ' + status }); + if (result.event === "runner_on_no_hosts") { + msg = "No hosts remaining"; + } + if (result.event === 'runner_on_unreachable') { + status = 'unreachable'; + status_text = 'Unreachable'; + } + else if (result.failed) { + status = 'failed'; + status_text = 'Failed'; + } + else if (result.changed) { + status = 'changed'; + status_text = 'Changed'; + } + if (result.event_data.res && result.event_data.res.msg) { + msg = result.event_data.res.msg; + } + result.msg = msg; + result.status = status; + result.status_text = status_text; + return result; + }; + + buildRow = function(res) { + var html = ''; + html += "\n"; + html += " " + res.status_text + "\n"; + html += "" + res.host_name + "\n"; + html += "" + res.play + "\n"; + html += "" + res.task + "\n"; + html += ""; + return html; + }; + + buildTable = function(data) { + var html = "\n"; + html += "\n"; + data.results.forEach(function(result) { + var res = setStatus(result); + html += buildRow(res); + }); + html += "\n"; + html += "
\n"; + return html; + }; + + fixHeight = function() { + var available_height = $('#host-events-modal-dialog').height() - $('#host-events-modal-dialog #search-form').height() - $('#host-events-modal-dialog #fixed-table-header').height(); + $('#host-events').height(available_height); + $log.debug('set height to: ' + available_height); + // Check width and reset search fields + if ($('#host-events-modal-dialog').width() <= 450) { + $('#host-events-modal-dialog #status-field').css({'margin-left': '7px'}); + } + else { + $('#host-events-modal-dialog #status-field').css({'margin-left': '15px'}); + } + }; + + GetEvents({ + url: url, + scope: scope, + callback: 'EventsReady' }); - }; - }]); + + scope.modalOK = function() { + $('#host-events-modal-dialog').dialog('close'); + scope.$destroy(); + }; + + scope.searchEvents = function() { + scope.eventsSearchActive = (scope.host_events_search_name) ? true : false; + GetEvents({ + scope: scope, + url: url, + callback: 'RefreshHTML' + }); + }; + + scope.searchEventKeyPress = function(e) { + if (e.keyCode === 13) { + scope.searchEvents(); + } + }; + + scope.showDetails = function(id) { + EventViewer({ + scope: parent_scope, + url: GetBasePath('jobs') + job_id + '/job_events/?id=' + id, + }); + }; + + if (scope.removeEventsScrollDownBuild) { + scope.removeEventsScrollDownBuild(); + } + scope.removeEventsScrollDownBuild = scope.$on('EventScrollDownBuild', function(e, data, maxID) { + var elem, html = ''; + lastID = maxID; + data.results.forEach(function(result) { + var res = setStatus(result); + html += buildRow(res); + }); + if (html) { + $('#host-events table tbody').append(html); + elem = angular.element(document.getElementById('host-events')); + $compile(elem)(scope); + } + }); + + scope.hostEventsScrollDown = function() { + GetEvents({ + scope: scope, + url: url, + gt: lastID, + callback: 'EventScrollDownBuild' + }); + }; + + }; + }]) + + .factory('GetEvents', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { + return function(params) { + var url = params.url, + scope = params.scope, + gt = params.gt, + callback = params.callback; + + if (scope.host_events_search_name) { + url += '?host_name=' + scope.host_events_search_name; + } + else { + url += '?host_name__isnull=false'; + } + + if (scope.host_events_search_status.value === 'changed') { + url += '&event__icontains=runner&changed=true'; + } + else if (scope.host_events_search_status.value === 'failed') { + url += '&event__icontains=runner&failed=true'; + } + else if (scope.host_events_search_status.value === 'ok') { + url += '&event=runner_on_ok&changed=false'; + } + else if (scope.host_events_search_status.value === 'unreachable') { + url += '&event=runner_on_unreachable'; + } + else if (scope.host_events_search_status.value === 'all') { + url += '&event__icontains=runner¬__event=runner_on_skipped'; + } + + if (gt) { + // used for endless scroll + url += '&id__gt=' + gt; + } + + url += '&page_size=50&order=id'; + + scope.hostViewSearching = true; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + var lastID; + scope.hostViewSearching = false; + if (data.results.length > 0) { + lastID = data.results[data.results.length - 1].id; + } + scope.$emit(callback, data, lastID); + }) + .error(function(data, status) { + scope.hostViewSearching = false; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get events ' + url + '. GET returned: ' + status }); + }); + }; + }]); diff --git a/awx/ui/static/js/helpers/Hosts.js b/awx/ui/static/js/helpers/Hosts.js index 3d69b860d9..280e398599 100644 --- a/awx/ui/static/js/helpers/Hosts.js +++ b/awx/ui/static/js/helpers/Hosts.js @@ -8,20 +8,21 @@ */ /* jshint loopfunc: true */ - /** +/** * @ngdoc function * @name helpers.function:Hosts * @description Routines that handle host add/edit/delete on the Inventory detail page. -*/ - + */ +'use strict'; +export default angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'HostListDefinition', - 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'AuthService', 'HostsHelper', - 'InventoryHelper', 'RelatedSearchHelper', 'InventoryFormDefinition', 'SelectionHelper', - 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'LogViewerHelper', - 'GroupListDefinition' - ]) + 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'AuthService', 'HostsHelper', + 'InventoryHelper', 'RelatedSearchHelper', 'InventoryFormDefinition', 'SelectionHelper', + 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'LogViewerHelper', + 'GroupListDefinition' +]) .factory('SetEnabledMsg', [ function() { return function(host) { @@ -65,8 +66,8 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H return function(params) { var scope = params.scope, - host = params.host, - i, html, title; + host = params.host, + i, html, title; function ellipsis(a) { if (a.length > 25) { @@ -96,9 +97,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H // build html table of job status info jobs = host.summary_fields.recent_jobs.sort( function(a,b) { - // reverse numerical order - return -1 * (a - b); - }); + // reverse numerical order + return -1 * (a - b); + }); title = "Recent Jobs"; html = "\n"; html += "\n"; @@ -161,7 +162,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H .factory('ViewJob', ['LogViewer', 'GetBasePath', function(LogViewer, GetBasePath) { return function(params) { var scope = params.scope, - id = params.id; + id = params.id; LogViewer({ scope: scope, url: GetBasePath('jobs') + id + '/' @@ -170,1143 +171,1138 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H }]) .factory('HostsReload', [ '$routeParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'SearchInit', 'PaginateInit', 'Wait', - 'SetHostStatus', 'SetStatus', 'ApplyEllipsis', 'SetContainerHeights', 'GetHostContainerRows', -function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateInit, Wait, SetHostStatus, SetStatus, - ApplyEllipsis, SetContainerHeights) { - return function(params) { - - var scope = params.scope, - parent_scope = params.parent_scope, - group_id = params.group_id, - inventory_id = params.inventory_id, - list = InventoryHosts, - pageSize = (params.pageSize) ? params.pageSize : 20, - - url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' : - GetBasePath('inventory') + inventory_id + '/hosts/'; - - 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'); - } - } - }); - - // Size containers based on viewport - SetContainerHeights({ scope: scope, reloadHosts: false }); - - SearchInit({ scope: scope, set: 'hosts', list: list, url: url }); - PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize }); - - if ($routeParams.host_name) { - scope[list.iterator + 'InputDisable'] = false; - scope[list.iterator + 'SearchValue'] = $routeParams.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('InjectHosts', ['GenerateList', 'InventoryHosts', 'HostsReload', -function(GenerateList, InventoryHosts, HostsReload) { - return function(params) { - - var group_scope = params.group_scope, - host_scope = params.host_scope, - inventory_id = params.inventory_id, - group_id = params.group_id, - pageSize = params.pageSize, - generator = GenerateList; - - // Inject the list html - generator.inject(InventoryHosts, { scope: host_scope, mode: 'edit', id: 'hosts-container', breadCrumbs: false, searchSize: 'col-lg-6 col-md-6 col-sm-6' }); - - // Load data - HostsReload({ scope: host_scope, group_id: group_id, inventory_id: inventory_id, parent_scope: group_scope, pageSize: pageSize }); - }; -}]) - -.factory('ToggleHostEnabled', [ 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Alert', 'Find', 'SetEnabledMsg', -function(GetBasePath, Rest, Wait, ProcessErrors, Alert, Find, SetEnabledMsg) { - return function(params) { - - var id = params.host_id, - external_source = params.external_source, - parent_scope = params.parent_scope, - host_scope = params.host_scope, - host; - - function setMsg(host) { - host.enabled = (host.enabled) ? false : true; - host.enabled_flag = host.enabled; - SetEnabledMsg(host); - } - - if (!external_source) { - // Host is not managed by an external source - Wait('start'); - host = Find({ list: host_scope.hosts, key: 'id', val: id }); - setMsg(host); - - Rest.setUrl(GetBasePath('hosts') + id + '/'); - Rest.put(host) - .success( function() { - Wait('stop'); - }) - .error( function(data, status) { - // Flip the enabled flag back - setMsg(host); - ProcessErrors(parent_scope, data, status, null, - { hdr: 'Error!', msg: 'Failed to update host. PUT returned status: ' + status }); - }); - } - else { - Alert('Action Not Allowed', 'This host is managed by an external cloud source. Disable it at the external source, ' + - 'then run an inventory sync to update Tower with the new status.', 'alert-info'); - } - }; -}]) - -.factory('HostsList', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostList', 'GenerateList', - 'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'HostsAdd', 'HostsReload', 'SelectionInit', -function($rootScope, $location, $log, $routeParams, Rest, Alert, HostList, GenerateList, Prompt, SearchInit, - PaginateInit, ProcessErrors, GetBasePath, HostsAdd, HostsReload, SelectionInit) { - return function(params) { - - var inventory_id = params.inventory_id, - group_id = params.group_id, - list = HostList, - generator = GenerateList, - defaultUrl, scope; - - list.iterator = 'subhost'; //Override the iterator and name so the scope of the modal dialog - list.name = 'subhosts'; //will not conflict with the parent scope - - - - scope = generator.inject(list, { - id: 'form-modal-body', - mode: 'select', - breadCrumbs: false, - selectButton: false - }); - - defaultUrl = GetBasePath('inventory') + inventory_id + '/hosts/?not__groups__id=' + scope.group_id; - - scope.formModalActionLabel = 'Select'; - scope.formModalHeader = 'Add Existing Hosts'; - scope.formModalCancelShow = true; - - SelectionInit({ scope: scope, list: list, url: GetBasePath('groups') + group_id + '/hosts/' }); - - if (scope.removeModalClosed) { - scope.removeModalClosed(); - } - scope.removeModalClosed = scope.$on('modalClosed', function() { - // if the modal closed, assume something got changed and reload the host list - HostsReload(params); - }); - - $('.popover').popover('hide'); //remove any lingering pop-overs - $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); - $('#form-modal').modal({ backdrop: 'static', keyboard: false }); - - SearchInit({ scope: scope, set: 'subhosts', list: list, url: defaultUrl }); - PaginateInit({ scope: scope, list: list, url: defaultUrl, mode: 'lookup' }); - scope.search(list.iterator); - - if (!scope.$$phase) { - scope.$digest(); - } - - scope.createHost = function() { - $('#form-modal').modal('hide'); - HostsAdd({ scope: params.scope, inventory_id: inventory_id, group_id: group_id }); - }; - - }; -}]) - - -.factory('HostsCreate', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'ToJSON', -function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, HostsReload, ParseTypeChange, Wait, ToJSON) { - return function(params) { - - var parent_scope = params.scope, - inventory_id = parent_scope.inventory_id, - group_id = parent_scope.selected_group_id, - defaultUrl = GetBasePath('groups') + group_id + '/hosts/', - form = HostForm, - generator = GenerateForm, - scope = generator.inject(form, {mode: 'add', modal: true, related: false}), - master={}; - - scope.formModalActionLabel = 'Save'; - scope.formModalHeader = 'Create New Host'; - scope.formModalCancelShow = true; - - scope.parseType = 'yaml'; - ParseTypeChange({ scope: scope, field_id: 'host_variables' }); - - if (scope.removeHostsReload) { - scope.removeHostsReload(); - } - scope.removeHostsReload = scope.$on('hostsReload', function() { - HostsReload(params); - }); - - $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); - //$('#form-modal').unbind('hidden'); - //$('#form-modal').on('hidden', function () { scope.$emit('hostsReload'); }); - - generator.reset(); - master={}; - - if (!scope.$$phase) { - scope.$digest(); - } - - if (scope.removeHostSaveComplete) { - scope.removeHostSaveComplete(); - } - scope.removeHostSaveComplete = scope.$on('HostSaveComplete', function() { - Wait('stop'); - $('#form-modal').modal('hide'); - - HostsReload({ - scope: parent_scope, - group_id: parent_scope.selected_group_id, - tree_id: parent_scope.selected_tree_id, - inventory_id: parent_scope.inventory_id - }); - - //WatchInventoryWindowResize(); - }); - - // Save - scope.formModalAction = function() { - - Wait('start'); - - var fld, data={}; - scope.formModalActionDisabled = true; - data.variables = ToJSON(scope.parseType, scope.variables, true); - for (fld in form.fields) { - if (fld !== 'variables') { - data[fld] = scope[fld]; - } - } - data.inventory = inventory_id; - - Rest.setUrl(defaultUrl); - Rest.post(data) - .success( function() { - scope.$emit('HostSaveComplete'); - }) - .error( function(data, status) { - Wait('stop'); - scope.formModalActionDisabled = false; - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to add new host. POST returned status: ' + status }); - }); - }; - - // Cancel - scope.formReset = function() { - // Defaults - generator.reset(); - }; - - scope.cancelModal = function() { - // WatchInventoryWindowResize(); - }; - - }; -}]) - - -.factory('HostsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis', - 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', -function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON, - ParseVariableString, CreateDialog, TextareaResize) { - return function(params) { - - var parent_scope = params.host_scope, - group_scope = params.group_scope, - host_id = params.host_id, - inventory_id = params.inventory_id, - mode = params.mode, // 'add' or 'edit' - selected_group_id = params.selected_group_id, - generator = GenerateForm, - form = HostForm, - defaultUrl, - scope = parent_scope.$new(), - master = {}, - relatedSets = {}, - buttons, url; - - generator.inject(HostForm, { mode: 'edit', id: 'host-modal-dialog', breadCrumbs: false, related: false, scope: scope }); - generator.reset(); - - buttons = [{ - label: "Cancel", - onClick: function() { - scope.cancelModal(); - }, - icon: "fa-times", - "class": "btn btn-default", - "id": "host-cancel-button" - },{ - label: "Save", - onClick: function() { - scope.saveModal(); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "host-save-button" - }]; - - CreateDialog({ - scope: scope, - buttons: buttons, - width: 675, - height: 750, - minWidth: 400, - title: 'Host Properties', - id: 'host-modal-dialog', - clonseOnEscape: false, - onClose: function() { - Wait('stop'); - scope.codeMirror.destroy(); - $('#host-modal-dialog').empty(); - }, - onResizeStop: function() { - TextareaResize({ - scope: scope, - textareaId: 'host_variables', - modalId: 'host-modal-dialog', - formId: 'host_form' - }); - }, - beforeDestroy: function() { - if (scope.codeMirror) { - scope.codeMirror.destroy(); - } - $('#host-modal-dialog').empty(); - }, - onOpen: function() { - $('#host_name').focus(); - }, - callback: 'HostEditDialogReady' - }); - - scope.parseType = 'yaml'; - - if (scope.hostVariablesLoadedRemove) { - scope.hostVariablesLoadedRemove(); - } - scope.hostVariablesLoadedRemove = scope.$on('hostVariablesLoaded', function() { - $('#host-modal-dialog').dialog('open'); - setTimeout(function() { - TextareaResize({ - scope: scope, - textareaId: 'host_variables', - modalId: 'host-modal-dialog', - formId: 'host_form', - parse: true - }); - }, 300); - //ParseTypeChange({ scope: scope, field_id: 'host_variables', onReady: callback }); - }); - - if (scope.hostLoadedRemove) { - scope.hostLoadedRemove(); - } - scope.hostLoadedRemove = scope.$on('hostLoaded', function() { - // Retrieve host variables - if (scope.variable_url) { - Rest.setUrl(scope.variable_url); - Rest.get() - .success( function(data) { - scope.variables = ParseVariableString(data); - scope.$emit('hostVariablesLoaded'); - }) - .error( function(data, status) { - scope.variables = null; - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to retrieve host variables. GET returned status: ' + status }); - }); - } - else { - scope.variables = "---"; - scope.$emit('hostVariablesLoaded'); - } - master.variables = scope.variables; - }); - - Wait('start'); - - // Retrieve detail record and prepopulate the form - if (mode === 'edit') { - defaultUrl = GetBasePath('hosts') + host_id + '/'; - Rest.setUrl(defaultUrl); - Rest.get() - .success( function(data) { - var set, fld, related; - for (fld in form.fields) { - if (data[fld]) { - scope[fld] = data[fld]; - master[fld] = scope[fld]; - } - } - related = data.related; - for (set in form.related) { - if (related[set]) { - relatedSets[set] = { url: related[set], iterator: form.related[set].iterator }; - } - } - scope.variable_url = data.related.variable_data; - scope.has_inventory_sources = data.has_inventory_sources; - scope.$emit('hostLoaded'); - }) - .error( function(data, status) { - ProcessErrors(parent_scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to retrieve host: ' + host_id + '. GET returned status: ' + status }); - }); - } - else { - // Add mode - url = GetBasePath('groups') + selected_group_id + '/'; - Rest.setUrl(url); - Rest.get() - .success( function(data) { - scope.has_inventory_sources = data.has_inventory_sources; - scope.enabled = true; - scope.variables = '---'; - defaultUrl = data.related.hosts; - scope.$emit('hostVariablesLoaded'); - }) - .error( function(data, status) { - ProcessErrors(parent_scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to retrieve group: ' + selected_group_id + '. GET returned status: ' + status }); - }); - } - - if (scope.removeSaveCompleted) { - scope.removeSaveCompleted(); - } - scope.removeSaveCompleted = scope.$on('saveCompleted', function() { - try { - $('#host-modal-dialog').dialog('close'); - } - catch(err) { - // ignore - } - if (group_scope && group_scope.refreshHosts) { - group_scope.refreshHosts(); - } - if (parent_scope.refreshHosts) { - parent_scope.refreshHosts(); - } - scope.$destroy(); - }); - - // Save changes to the parent - scope.saveModal = function() { - - Wait('start'); - var fld, data={}; - - try { - data.variables = ToJSON(scope.parseType, scope.variables, true); - for (fld in form.fields) { - data[fld] = scope[fld]; - } - data.inventory = inventory_id; - Rest.setUrl(defaultUrl); - if (mode === 'edit') { - Rest.put(data) - .success( function() { - scope.$emit('saveCompleted'); - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to update host: ' + host_id + '. PUT returned status: ' + status }); - }); - } - else { - Rest.post(data) - .success( function() { - scope.$emit('saveCompleted'); - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to create host. POST returned status: ' + status }); - }); - } - } - catch(e) { - // ignore. ToJSON will have already alerted the user - } - }; - - // Cancel - scope.formReset = function() { - generator.reset(); - for (var fld in master) { - scope[fld] = master[fld]; - } - scope.parseType = 'yaml'; - }; - - scope.cancelModal = function() { - try { - $('#host-modal-dialog').dialog('close'); - } - catch(err) { - // ignore - } - scope.$destroy(); - }; - - }; -}]) - - -.factory('HostsDelete', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'Wait', -function($rootScope, $location, $log, $routeParams, Rest, Alert, Prompt, ProcessErrors, GetBasePath, HostsReload, Wait) { - return function(params) { - // Remove the selected host from the current group by disassociating - - var action_to_take, body, - scope = params.parent_scope, - host_id = params.host_id, - host_name = params.host_name, - group, - url_list = []; - - if (scope.selected_group_id) { - //group = Find({ list: parent_scope.groups, key: 'id', val: parent_scope.selected_group_id }); - //getChildren(group.id); - url_list.push(GetBasePath('groups') + scope.selected_group_id + '/hosts/'); - } - else { - url_list.push(GetBasePath('inventory') + scope.inventory.id + '/hosts/'); - } - - if (scope.removeHostsReload) { - scope.removeHostsReload(); - } - scope.removeHostsReload = scope.$on('hostsReload', function() { - $('#prompt-modal').modal('hide'); - scope.refreshHosts(); - }); - - $('#prompt-modal').on('hidden.bs.modal', function(){ Wait('stop'); }); - - action_to_take = function() { - var count=0, i; - - Wait('start'); - - if (scope.removeHostRemoved) { - scope.removeHostRemoved(); - } - scope.removeHostRemoved = scope.$on('hostRemoved', function(){ - count++; - if (count === url_list.length) { - Wait('start'); - scope.$emit('hostsReload'); - } - }); - - for(i=0; i < url_list.length; i++) { - Rest.setUrl(url_list[i]); - Rest.post({ id: host_id, disassociate: 1 }) - .success( function() { - scope.$emit('hostRemoved'); - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Attempt to delete ' + host_name + ' failed. DELETE returned status: ' + status }); - }); - } - }; - - body = (group) ? '

Are you sure you want to remove host ' + host_name + ' from group ' + group.name + '?' + - ' It will still be part of the inventory and available in All Hosts.

' : - '

Are you sure you want to permanently delete host ' + host_name + ' from the inventory?

'; - Prompt({ hdr: 'Delete Host', body: body, action: action_to_take, 'class': 'btn-danger' }); - - }; -}]) - -.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'GenerateList', 'GroupList', 'SearchInit', - 'PaginateInit', - function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit) { - 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() { - var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/'; - GenerateList.inject(GroupList, { - mode: 'lookup', - id: 'copy-host-select-container', - scope: scope - //, - //instructions: instructions - }); - 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 - } - scope.searchCleanup(); - 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 === '0') { - 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 = ''; - } - } 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 === '1') { - 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 - }); - }); - } - }; - - - }; -}]) - -.factory('EditHostGroups', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GenerateForm', 'Prompt', - 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', -function($rootScope, $location, $log, $routeParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, - ParseTypeChange, Wait) { - return function(params) { - - var host_id = params.host_id, - inventory_id = params.inventory_id, - generator = GenerateForm, - actions = [], - i, html, defaultUrl, scope, postAction; - - html = "
\n"; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "
\n"; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "
\n"; - html += "
\n"; - html += "
\n"; - html += "
\n"; - html += "\n"; - html += "\n"; - html += "

(move selected groups)

\n"; - html += "
\n"; - html += "
\n"; - - defaultUrl = GetBasePath('hosts') + host_id + '/'; - scope = generator.inject(null, { mode: 'edit', modal: true, related: false, html: html }); - - for (i=0; i < scope.hosts.length; i++) { - if (scope.hosts[i].id === host_id) { - scope.host = scope.hosts[i]; - } - } - - scope.selectedGroups = null; - scope.assignedGroups = null; - scope.leftButtonDisabled = true; - scope.rightButtonDisabled = true; - - scope.formModalActionLabel = 'Save'; - //scope.formModalHeader = 'Host Groups'; - scope.formModalHeader = scope.host.name + ' - Groups'; - scope.formModalCancelShow = true; - scope.formModalActionDisabled = true; - - $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); - - if (scope.hostGroupChangeRemove) { - scope.hostGroupChangeRemove(); - } - scope.hostGroupChangeRemove = scope.$on('hostGroupChange', function() { - actions.pop(); - if (actions.length === 0) { - postAction = function() { - setTimeout(function() { Wait('stop'); }, 500); - }; - HostsReload({ scope: scope, inventory_id: inventory_id, group_id: scope.group_id , action: postAction }); - } - }); - - // Save changes - scope.formModalAction = function() { - var i, j, found; - - $('#form-modal').modal('hide'); - Wait('start'); - - // removed host from deleted groups - for (i=0; i < scope.original_groups.length; i++) { - found = false; - for (j=0; j < scope.host_groups.length; j++) { - if (scope.original_groups[i].id === scope.host_groups[j].id) { - found = true; - } - } - if (!found) { - // group was removed - actions.push({ group_id: scope.original_groups[i].id , action: 'delete' }); - Rest.setUrl(GetBasePath('groups') + scope.original_groups[i].id + '/hosts/'); - Rest.post({ id: host_id, disassociate: 1 }) - .success( function() { - scope.$emit('hostGroupChange'); - }) - .error( function(data, status) { - scope.$emit('hostGroupChange'); - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Attempt to remove host from group ' + scope.original_groups[i].name + - ' failed. POST returned status: ' + status }); - }); - } - } - - // add host to new groups - for (i=0; i < scope.host_groups.length; i++) { - found = false; - for (j=0; j < scope.original_groups.length; j++) { - if (scope.original_groups[j].id === scope.host_groups[i].id) { - found = true; - } - } - if (!found) { - // group was added - actions.push({ group_id: scope.host_groups[i].id , action: 'add' }); - Rest.setUrl(GetBasePath('groups') + scope.host_groups[i].id + '/hosts/'); - Rest.post(scope.host) - .success( function() { - scope.$emit('hostGroupChange'); - }) - .error( function(data, status) { - scope.$emit('hostGroupChange'); - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Attempt to add host to group ' + scope.host_groups[i].name + - ' failed. POST returned status: ' + status }); - }); - } - } - }; - - scope.leftChange = function() { - // Select/deselect on available groups list - if (scope.selectedGroups !== null && scope.selectedGroups.length > 0) { - scope.assignedGroups = null; - scope.leftButtonDisabled = true; - scope.rightButtonDisabled = false; - } - else { - scope.rightButtonDisabled = true; - } - }; - - scope.rightChange = function() { - // Select/deselect made on host groups list - if (scope.assignedGroups !== null && scope.assignedGroups.length > 0) { - scope.selectedGroups = null; - scope.leftButtonDisabled = false; - scope.rightButtonDisabled = true; - } - else { - scope.leftButtonDisabled = true; - } - }; - - scope.moveLeft = function() { - // Remove selected groups from the list of assigned groups - - var i, j, found, placed; - - for (i=0; i < scope.assignedGroups.length; i++){ - for (j=0 ; j < scope.host_groups.length; j++) { - if (scope.host_groups[j].id === scope.assignedGroups[i].id) { - scope.host_groups.splice(j,1); - break; - } - } - } - for (i=0; i < scope.assignedGroups.length; i++){ - found = false; - for (j=0; j < scope.available_groups.length && !found; j++){ - if (scope.available_groups[j].id === scope.assignedGroups[i].id) { - found=true; - } - } - if (!found) { - placed = false; - for (j=0; j < scope.available_groups.length && !placed; j++){ - if (j === 0 && scope.assignedGroups[i].name.toLowerCase() < scope.available_groups[j].name.toLowerCase()) { - // prepend to the beginning of the array - placed=true; - scope.available_groups.unshift(scope.assignedGroups[i]); - } - else if (j + 1 < scope.available_groups.length) { - if (scope.assignedGroups[i].name.toLowerCase() > scope.available_groups[j].name.toLowerCase() && - scope.assignedGroups[i].name.toLowerCase() < scope.available_groups[j + 1].name.toLowerCase() ) { - // insert into the middle of the array - placed = true; - scope.available_groups.splice(j + 1, 0, scope.assignedGroups[i]); - } - } - } - if (!placed) { - // append to the end of the array - scope.available_groups.push(scope.assignedGroups[i]); - } - } - } - scope.assignedGroups = null; - scope.leftButtonDisabled = true; - scope.rightButtonDisabled = true; - scope.formModalActionDisabled = false; - }; - - scope.moveRight = function() { - // Remove selected groups from list of available groups - - var i, j, found, placed; - - for (i=0; i < scope.selectedGroups.length; i++){ - for (j=0 ; j < scope.available_groups.length; j++) { - if (scope.available_groups[j].id === scope.selectedGroups[i].id) { - scope.available_groups.splice(j,1); - break; - } - } - } - for (i=0; i < scope.selectedGroups.length; i++){ - found = false; - for (j=0; j < scope.host_groups.length && !found; j++){ - if (scope.host_groups[j].id === scope.selectedGroups[i].id) { - found=true; - } - } - if (!found) { - placed = false; - for (j=0; j < scope.host_groups.length && !placed; j++) { - if (j === 0 && scope.selectedGroups[i].name.toLowerCase() < scope.host_groups[j].name.toLowerCase()) { - // prepend to the beginning of the array - placed=true; - scope.host_groups.unshift(scope.selectedGroups[i]); - } - else if (j + 1 < scope.host_groups.length) { - if (scope.selectedGroups[i].name.toLowerCase() > scope.host_groups[j].name.toLowerCase() && - scope.selectedGroups[i].name.toLowerCase() < scope.host_groups[j + 1].name.toLowerCase() ) { - // insert into the middle of the array - placed = true; - scope.host_groups.splice(j + 1, 0, scope.selectedGroups[i]); - } - } - } - if (!placed) { - // append to the end of the array - scope.host_groups.push(scope.selectedGroups[i]); - } - } - } - scope.selectedGroups = null; - scope.leftButtonDisabled = true; - scope.rightButtonDisabled = true; - scope.formModalActionDisabled = false; - }; - - - // Load the host's current list of groups - scope.host_groups = []; - scope.original_groups = []; - scope.available_groups = []; - Rest.setUrl(scope.host.related.groups + '?order_by=name'); - Rest.get() - .success( function(data) { - var i, j, found; - for (i=0; i < data.results.length; i++) { - scope.host_groups.push({ - id: data.results[i].id, - name: data.results[i].name, - description: data.results[i].description - }); - scope.original_groups.push({ - id: data.results[i].id, - name: data.results[i].name, - description: data.results[i].description - }); - } - for (i=0; i < scope.inventory_groups.length; i++) { - found = false; - for (j=0; j < scope.host_groups.length; j++) { - if (scope.inventory_groups[i].id === scope.host_groups[j].id) { - found = true; - } - } - if (!found) { - scope.available_groups.push(scope.inventory_groups[i]); - } - } - }) - .error( function(data, status) { - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Failed to get current groups for host: ' + host_id + '. GET returned: ' + status }); - }); - - if (scope.removeHostsReload) { - scope.removeHostsReload(); - } - scope.removeHostsReload = scope.$on('hostsReload', function() { - HostsReload(params); - }); - - if (!scope.$$phase) { - scope.$digest(); - } - }; -}]); - - - - - + 'SetHostStatus', 'SetStatus', 'ApplyEllipsis', 'SetContainerHeights', 'GetHostContainerRows', + function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateInit, Wait, SetHostStatus, SetStatus, + ApplyEllipsis, SetContainerHeights) { + return function(params) { + + var scope = params.scope, + parent_scope = params.parent_scope, + group_id = params.group_id, + inventory_id = params.inventory_id, + list = InventoryHosts, + pageSize = (params.pageSize) ? params.pageSize : 20, + + url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' : + GetBasePath('inventory') + inventory_id + '/hosts/'; + + 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'); + } + } + }); + + // Size containers based on viewport + SetContainerHeights({ scope: scope, reloadHosts: false }); + + SearchInit({ scope: scope, set: 'hosts', list: list, url: url }); + PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize }); + + if ($routeParams.host_name) { + scope[list.iterator + 'InputDisable'] = false; + scope[list.iterator + 'SearchValue'] = $routeParams.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('InjectHosts', ['GenerateList', 'InventoryHosts', 'HostsReload', + function(GenerateList, InventoryHosts, HostsReload) { + return function(params) { + + var group_scope = params.group_scope, + host_scope = params.host_scope, + inventory_id = params.inventory_id, + group_id = params.group_id, + pageSize = params.pageSize, + generator = GenerateList; + + // Inject the list html + generator.inject(InventoryHosts, { scope: host_scope, mode: 'edit', id: 'hosts-container', breadCrumbs: false, searchSize: 'col-lg-6 col-md-6 col-sm-6' }); + + // Load data + HostsReload({ scope: host_scope, group_id: group_id, inventory_id: inventory_id, parent_scope: group_scope, pageSize: pageSize }); + }; + }]) + + .factory('ToggleHostEnabled', [ 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Alert', 'Find', 'SetEnabledMsg', + function(GetBasePath, Rest, Wait, ProcessErrors, Alert, Find, SetEnabledMsg) { + return function(params) { + + var id = params.host_id, + external_source = params.external_source, + parent_scope = params.parent_scope, + host_scope = params.host_scope, + host; + + function setMsg(host) { + host.enabled = (host.enabled) ? false : true; + host.enabled_flag = host.enabled; + SetEnabledMsg(host); + } + + if (!external_source) { + // Host is not managed by an external source + Wait('start'); + host = Find({ list: host_scope.hosts, key: 'id', val: id }); + setMsg(host); + + Rest.setUrl(GetBasePath('hosts') + id + '/'); + Rest.put(host) + .success( function() { + Wait('stop'); + }) + .error( function(data, status) { + // Flip the enabled flag back + setMsg(host); + ProcessErrors(parent_scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to update host. PUT returned status: ' + status }); + }); + } + else { + Alert('Action Not Allowed', 'This host is managed by an external cloud source. Disable it at the external source, ' + + 'then run an inventory sync to update Tower with the new status.', 'alert-info'); + } + }; + }]) + + .factory('HostsList', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostList', 'GenerateList', + 'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'HostsAdd', 'HostsReload', 'SelectionInit', + function($rootScope, $location, $log, $routeParams, Rest, Alert, HostList, GenerateList, Prompt, SearchInit, + PaginateInit, ProcessErrors, GetBasePath, HostsAdd, HostsReload, SelectionInit) { + return function(params) { + + var inventory_id = params.inventory_id, + group_id = params.group_id, + list = HostList, + generator = GenerateList, + defaultUrl, scope; + + list.iterator = 'subhost'; //Override the iterator and name so the scope of the modal dialog + list.name = 'subhosts'; //will not conflict with the parent scope + + + + scope = generator.inject(list, { + id: 'form-modal-body', + mode: 'select', + breadCrumbs: false, + selectButton: false + }); + + defaultUrl = GetBasePath('inventory') + inventory_id + '/hosts/?not__groups__id=' + scope.group_id; + + scope.formModalActionLabel = 'Select'; + scope.formModalHeader = 'Add Existing Hosts'; + scope.formModalCancelShow = true; + + SelectionInit({ scope: scope, list: list, url: GetBasePath('groups') + group_id + '/hosts/' }); + + if (scope.removeModalClosed) { + scope.removeModalClosed(); + } + scope.removeModalClosed = scope.$on('modalClosed', function() { + // if the modal closed, assume something got changed and reload the host list + HostsReload(params); + }); + + $('.popover').popover('hide'); //remove any lingering pop-overs + $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); + $('#form-modal').modal({ backdrop: 'static', keyboard: false }); + + SearchInit({ scope: scope, set: 'subhosts', list: list, url: defaultUrl }); + PaginateInit({ scope: scope, list: list, url: defaultUrl, mode: 'lookup' }); + scope.search(list.iterator); + + if (!scope.$$phase) { + scope.$digest(); + } + + scope.createHost = function() { + $('#form-modal').modal('hide'); + HostsAdd({ scope: params.scope, inventory_id: inventory_id, group_id: group_id }); + }; + + }; + }]) + + + .factory('HostsCreate', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'ToJSON', + function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, + GetBasePath, HostsReload, ParseTypeChange, Wait, ToJSON) { + return function(params) { + + var parent_scope = params.scope, + inventory_id = parent_scope.inventory_id, + group_id = parent_scope.selected_group_id, + defaultUrl = GetBasePath('groups') + group_id + '/hosts/', + form = HostForm, + generator = GenerateForm, + scope = generator.inject(form, {mode: 'add', modal: true, related: false}), + master={}; + + scope.formModalActionLabel = 'Save'; + scope.formModalHeader = 'Create New Host'; + scope.formModalCancelShow = true; + + scope.parseType = 'yaml'; + ParseTypeChange({ scope: scope, field_id: 'host_variables' }); + + if (scope.removeHostsReload) { + scope.removeHostsReload(); + } + scope.removeHostsReload = scope.$on('hostsReload', function() { + HostsReload(params); + }); + + $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); + //$('#form-modal').unbind('hidden'); + //$('#form-modal').on('hidden', function () { scope.$emit('hostsReload'); }); + + generator.reset(); + master={}; + + if (!scope.$$phase) { + scope.$digest(); + } + + if (scope.removeHostSaveComplete) { + scope.removeHostSaveComplete(); + } + scope.removeHostSaveComplete = scope.$on('HostSaveComplete', function() { + Wait('stop'); + $('#form-modal').modal('hide'); + + HostsReload({ + scope: parent_scope, + group_id: parent_scope.selected_group_id, + tree_id: parent_scope.selected_tree_id, + inventory_id: parent_scope.inventory_id + }); + + //WatchInventoryWindowResize(); + }); + + // Save + scope.formModalAction = function() { + + Wait('start'); + + var fld, data={}; + scope.formModalActionDisabled = true; + data.variables = ToJSON(scope.parseType, scope.variables, true); + for (fld in form.fields) { + if (fld !== 'variables') { + data[fld] = scope[fld]; + } + } + data.inventory = inventory_id; + + Rest.setUrl(defaultUrl); + Rest.post(data) + .success( function() { + scope.$emit('HostSaveComplete'); + }) + .error( function(data, status) { + Wait('stop'); + scope.formModalActionDisabled = false; + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to add new host. POST returned status: ' + status }); + }); + }; + + // Cancel + scope.formReset = function() { + // Defaults + generator.reset(); + }; + + scope.cancelModal = function() { + // WatchInventoryWindowResize(); + }; + + }; + }]) + + + .factory('HostsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis', + 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', + function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, + GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON, + ParseVariableString, CreateDialog, TextareaResize) { + return function(params) { + + var parent_scope = params.host_scope, + group_scope = params.group_scope, + host_id = params.host_id, + inventory_id = params.inventory_id, + mode = params.mode, // 'add' or 'edit' + selected_group_id = params.selected_group_id, + generator = GenerateForm, + form = HostForm, + defaultUrl, + scope = parent_scope.$new(), + master = {}, + relatedSets = {}, + buttons, url; + + generator.inject(HostForm, { mode: 'edit', id: 'host-modal-dialog', breadCrumbs: false, related: false, scope: scope }); + generator.reset(); + + buttons = [{ + label: "Cancel", + onClick: function() { + scope.cancelModal(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "host-cancel-button" + },{ + label: "Save", + onClick: function() { + scope.saveModal(); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "host-save-button" + }]; + + CreateDialog({ + scope: scope, + buttons: buttons, + width: 675, + height: 750, + minWidth: 400, + title: 'Host Properties', + id: 'host-modal-dialog', + clonseOnEscape: false, + onClose: function() { + Wait('stop'); + scope.codeMirror.destroy(); + $('#host-modal-dialog').empty(); + }, + onResizeStop: function() { + TextareaResize({ + scope: scope, + textareaId: 'host_variables', + modalId: 'host-modal-dialog', + formId: 'host_form' + }); + }, + beforeDestroy: function() { + if (scope.codeMirror) { + scope.codeMirror.destroy(); + } + $('#host-modal-dialog').empty(); + }, + onOpen: function() { + $('#host_name').focus(); + }, + callback: 'HostEditDialogReady' + }); + + scope.parseType = 'yaml'; + + if (scope.hostVariablesLoadedRemove) { + scope.hostVariablesLoadedRemove(); + } + scope.hostVariablesLoadedRemove = scope.$on('hostVariablesLoaded', function() { + $('#host-modal-dialog').dialog('open'); + setTimeout(function() { + TextareaResize({ + scope: scope, + textareaId: 'host_variables', + modalId: 'host-modal-dialog', + formId: 'host_form', + parse: true + }); + }, 300); + //ParseTypeChange({ scope: scope, field_id: 'host_variables', onReady: callback }); + }); + + if (scope.hostLoadedRemove) { + scope.hostLoadedRemove(); + } + scope.hostLoadedRemove = scope.$on('hostLoaded', function() { + // Retrieve host variables + if (scope.variable_url) { + Rest.setUrl(scope.variable_url); + Rest.get() + .success( function(data) { + scope.variables = ParseVariableString(data); + scope.$emit('hostVariablesLoaded'); + }) + .error( function(data, status) { + scope.variables = null; + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve host variables. GET returned status: ' + status }); + }); + } + else { + scope.variables = "---"; + scope.$emit('hostVariablesLoaded'); + } + master.variables = scope.variables; + }); + + Wait('start'); + + // Retrieve detail record and prepopulate the form + if (mode === 'edit') { + defaultUrl = GetBasePath('hosts') + host_id + '/'; + Rest.setUrl(defaultUrl); + Rest.get() + .success( function(data) { + var set, fld, related; + for (fld in form.fields) { + if (data[fld]) { + scope[fld] = data[fld]; + master[fld] = scope[fld]; + } + } + related = data.related; + for (set in form.related) { + if (related[set]) { + relatedSets[set] = { url: related[set], iterator: form.related[set].iterator }; + } + } + scope.variable_url = data.related.variable_data; + scope.has_inventory_sources = data.has_inventory_sources; + scope.$emit('hostLoaded'); + }) + .error( function(data, status) { + ProcessErrors(parent_scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve host: ' + host_id + '. GET returned status: ' + status }); + }); + } + else { + // Add mode + url = GetBasePath('groups') + selected_group_id + '/'; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + scope.has_inventory_sources = data.has_inventory_sources; + scope.enabled = true; + scope.variables = '---'; + defaultUrl = data.related.hosts; + scope.$emit('hostVariablesLoaded'); + }) + .error( function(data, status) { + ProcessErrors(parent_scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve group: ' + selected_group_id + '. GET returned status: ' + status }); + }); + } + + if (scope.removeSaveCompleted) { + scope.removeSaveCompleted(); + } + scope.removeSaveCompleted = scope.$on('saveCompleted', function() { + try { + $('#host-modal-dialog').dialog('close'); + } + catch(err) { + // ignore + } + if (group_scope && group_scope.refreshHosts) { + group_scope.refreshHosts(); + } + if (parent_scope.refreshHosts) { + parent_scope.refreshHosts(); + } + scope.$destroy(); + }); + + // Save changes to the parent + scope.saveModal = function() { + + Wait('start'); + var fld, data={}; + + try { + data.variables = ToJSON(scope.parseType, scope.variables, true); + for (fld in form.fields) { + data[fld] = scope[fld]; + } + data.inventory = inventory_id; + Rest.setUrl(defaultUrl); + if (mode === 'edit') { + Rest.put(data) + .success( function() { + scope.$emit('saveCompleted'); + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to update host: ' + host_id + '. PUT returned status: ' + status }); + }); + } + else { + Rest.post(data) + .success( function() { + scope.$emit('saveCompleted'); + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to create host. POST returned status: ' + status }); + }); + } + } + catch(e) { + // ignore. ToJSON will have already alerted the user + } + }; + + // Cancel + scope.formReset = function() { + generator.reset(); + for (var fld in master) { + scope[fld] = master[fld]; + } + scope.parseType = 'yaml'; + }; + + scope.cancelModal = function() { + try { + $('#host-modal-dialog').dialog('close'); + } + catch(err) { + // ignore + } + scope.$destroy(); + }; + + }; + }]) + + + .factory('HostsDelete', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'Wait', + function($rootScope, $location, $log, $routeParams, Rest, Alert, Prompt, ProcessErrors, GetBasePath, HostsReload, Wait) { + return function(params) { + // Remove the selected host from the current group by disassociating + + var action_to_take, body, + scope = params.parent_scope, + host_id = params.host_id, + host_name = params.host_name, + group, + url_list = []; + + if (scope.selected_group_id) { + //group = Find({ list: parent_scope.groups, key: 'id', val: parent_scope.selected_group_id }); + //getChildren(group.id); + url_list.push(GetBasePath('groups') + scope.selected_group_id + '/hosts/'); + } + else { + url_list.push(GetBasePath('inventory') + scope.inventory.id + '/hosts/'); + } + + if (scope.removeHostsReload) { + scope.removeHostsReload(); + } + scope.removeHostsReload = scope.$on('hostsReload', function() { + $('#prompt-modal').modal('hide'); + scope.refreshHosts(); + }); + + $('#prompt-modal').on('hidden.bs.modal', function(){ Wait('stop'); }); + + action_to_take = function() { + var count=0, i; + + Wait('start'); + + if (scope.removeHostRemoved) { + scope.removeHostRemoved(); + } + scope.removeHostRemoved = scope.$on('hostRemoved', function(){ + count++; + if (count === url_list.length) { + Wait('start'); + scope.$emit('hostsReload'); + } + }); + + for(i=0; i < url_list.length; i++) { + Rest.setUrl(url_list[i]); + Rest.post({ id: host_id, disassociate: 1 }) + .success( function() { + scope.$emit('hostRemoved'); + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Attempt to delete ' + host_name + ' failed. DELETE returned status: ' + status }); + }); + } + }; + + body = (group) ? '

Are you sure you want to remove host ' + host_name + ' from group ' + group.name + '?' + + ' It will still be part of the inventory and available in All Hosts.

' : + '

Are you sure you want to permanently delete host ' + host_name + ' from the inventory?

'; + Prompt({ hdr: 'Delete Host', body: body, action: action_to_take, 'class': 'btn-danger' }); + + }; + }]) + + .factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'GenerateList', 'GroupList', 'SearchInit', + 'PaginateInit', + function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit) { + 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() { + var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/'; + GenerateList.inject(GroupList, { + mode: 'lookup', + id: 'copy-host-select-container', + scope: scope + //, + //instructions: instructions + }); + 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 + } + scope.searchCleanup(); + 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 === '0') { + 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 = ''; + } + } 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 === '1') { + 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 + }); + }); + } + }; + + + }; + }]) + + .factory('EditHostGroups', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GenerateForm', 'Prompt', + 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', + function($rootScope, $location, $log, $routeParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, + ParseTypeChange, Wait) { + return function(params) { + + var host_id = params.host_id, + inventory_id = params.inventory_id, + generator = GenerateForm, + actions = [], + i, html, defaultUrl, scope, postAction; + + html = "
\n"; + html += "
\n"; + html += "\n"; + html += "\n"; + html += "
\n"; + html += "
\n"; + html += "\n"; + html += "\n"; + html += "
\n"; + html += "
\n"; + html += "
\n"; + html += "
\n"; + html += "\n"; + html += "\n"; + html += "

(move selected groups)

\n"; + html += "
\n"; + html += "
\n"; + + defaultUrl = GetBasePath('hosts') + host_id + '/'; + scope = generator.inject(null, { mode: 'edit', modal: true, related: false, html: html }); + + for (i=0; i < scope.hosts.length; i++) { + if (scope.hosts[i].id === host_id) { + scope.host = scope.hosts[i]; + } + } + + scope.selectedGroups = null; + scope.assignedGroups = null; + scope.leftButtonDisabled = true; + scope.rightButtonDisabled = true; + + scope.formModalActionLabel = 'Save'; + //scope.formModalHeader = 'Host Groups'; + scope.formModalHeader = scope.host.name + ' - Groups'; + scope.formModalCancelShow = true; + scope.formModalActionDisabled = true; + + $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); + + if (scope.hostGroupChangeRemove) { + scope.hostGroupChangeRemove(); + } + scope.hostGroupChangeRemove = scope.$on('hostGroupChange', function() { + actions.pop(); + if (actions.length === 0) { + postAction = function() { + setTimeout(function() { Wait('stop'); }, 500); + }; + HostsReload({ scope: scope, inventory_id: inventory_id, group_id: scope.group_id , action: postAction }); + } + }); + + // Save changes + scope.formModalAction = function() { + var i, j, found; + + $('#form-modal').modal('hide'); + Wait('start'); + + // removed host from deleted groups + for (i=0; i < scope.original_groups.length; i++) { + found = false; + for (j=0; j < scope.host_groups.length; j++) { + if (scope.original_groups[i].id === scope.host_groups[j].id) { + found = true; + } + } + if (!found) { + // group was removed + actions.push({ group_id: scope.original_groups[i].id , action: 'delete' }); + Rest.setUrl(GetBasePath('groups') + scope.original_groups[i].id + '/hosts/'); + Rest.post({ id: host_id, disassociate: 1 }) + .success( function() { + scope.$emit('hostGroupChange'); + }) + .error( function(data, status) { + scope.$emit('hostGroupChange'); + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Attempt to remove host from group ' + scope.original_groups[i].name + + ' failed. POST returned status: ' + status }); + }); + } + } + + // add host to new groups + for (i=0; i < scope.host_groups.length; i++) { + found = false; + for (j=0; j < scope.original_groups.length; j++) { + if (scope.original_groups[j].id === scope.host_groups[i].id) { + found = true; + } + } + if (!found) { + // group was added + actions.push({ group_id: scope.host_groups[i].id , action: 'add' }); + Rest.setUrl(GetBasePath('groups') + scope.host_groups[i].id + '/hosts/'); + Rest.post(scope.host) + .success( function() { + scope.$emit('hostGroupChange'); + }) + .error( function(data, status) { + scope.$emit('hostGroupChange'); + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Attempt to add host to group ' + scope.host_groups[i].name + + ' failed. POST returned status: ' + status }); + }); + } + } + }; + + scope.leftChange = function() { + // Select/deselect on available groups list + if (scope.selectedGroups !== null && scope.selectedGroups.length > 0) { + scope.assignedGroups = null; + scope.leftButtonDisabled = true; + scope.rightButtonDisabled = false; + } + else { + scope.rightButtonDisabled = true; + } + }; + + scope.rightChange = function() { + // Select/deselect made on host groups list + if (scope.assignedGroups !== null && scope.assignedGroups.length > 0) { + scope.selectedGroups = null; + scope.leftButtonDisabled = false; + scope.rightButtonDisabled = true; + } + else { + scope.leftButtonDisabled = true; + } + }; + + scope.moveLeft = function() { + // Remove selected groups from the list of assigned groups + + var i, j, found, placed; + + for (i=0; i < scope.assignedGroups.length; i++){ + for (j=0 ; j < scope.host_groups.length; j++) { + if (scope.host_groups[j].id === scope.assignedGroups[i].id) { + scope.host_groups.splice(j,1); + break; + } + } + } + for (i=0; i < scope.assignedGroups.length; i++){ + found = false; + for (j=0; j < scope.available_groups.length && !found; j++){ + if (scope.available_groups[j].id === scope.assignedGroups[i].id) { + found=true; + } + } + if (!found) { + placed = false; + for (j=0; j < scope.available_groups.length && !placed; j++){ + if (j === 0 && scope.assignedGroups[i].name.toLowerCase() < scope.available_groups[j].name.toLowerCase()) { + // prepend to the beginning of the array + placed=true; + scope.available_groups.unshift(scope.assignedGroups[i]); + } + else if (j + 1 < scope.available_groups.length) { + if (scope.assignedGroups[i].name.toLowerCase() > scope.available_groups[j].name.toLowerCase() && + scope.assignedGroups[i].name.toLowerCase() < scope.available_groups[j + 1].name.toLowerCase() ) { + // insert into the middle of the array + placed = true; + scope.available_groups.splice(j + 1, 0, scope.assignedGroups[i]); + } + } + } + if (!placed) { + // append to the end of the array + scope.available_groups.push(scope.assignedGroups[i]); + } + } + } + scope.assignedGroups = null; + scope.leftButtonDisabled = true; + scope.rightButtonDisabled = true; + scope.formModalActionDisabled = false; + }; + + scope.moveRight = function() { + // Remove selected groups from list of available groups + + var i, j, found, placed; + + for (i=0; i < scope.selectedGroups.length; i++){ + for (j=0 ; j < scope.available_groups.length; j++) { + if (scope.available_groups[j].id === scope.selectedGroups[i].id) { + scope.available_groups.splice(j,1); + break; + } + } + } + for (i=0; i < scope.selectedGroups.length; i++){ + found = false; + for (j=0; j < scope.host_groups.length && !found; j++){ + if (scope.host_groups[j].id === scope.selectedGroups[i].id) { + found=true; + } + } + if (!found) { + placed = false; + for (j=0; j < scope.host_groups.length && !placed; j++) { + if (j === 0 && scope.selectedGroups[i].name.toLowerCase() < scope.host_groups[j].name.toLowerCase()) { + // prepend to the beginning of the array + placed=true; + scope.host_groups.unshift(scope.selectedGroups[i]); + } + else if (j + 1 < scope.host_groups.length) { + if (scope.selectedGroups[i].name.toLowerCase() > scope.host_groups[j].name.toLowerCase() && + scope.selectedGroups[i].name.toLowerCase() < scope.host_groups[j + 1].name.toLowerCase() ) { + // insert into the middle of the array + placed = true; + scope.host_groups.splice(j + 1, 0, scope.selectedGroups[i]); + } + } + } + if (!placed) { + // append to the end of the array + scope.host_groups.push(scope.selectedGroups[i]); + } + } + } + scope.selectedGroups = null; + scope.leftButtonDisabled = true; + scope.rightButtonDisabled = true; + scope.formModalActionDisabled = false; + }; + + + // Load the host's current list of groups + scope.host_groups = []; + scope.original_groups = []; + scope.available_groups = []; + Rest.setUrl(scope.host.related.groups + '?order_by=name'); + Rest.get() + .success( function(data) { + var i, j, found; + for (i=0; i < data.results.length; i++) { + scope.host_groups.push({ + id: data.results[i].id, + name: data.results[i].name, + description: data.results[i].description + }); + scope.original_groups.push({ + id: data.results[i].id, + name: data.results[i].name, + description: data.results[i].description + }); + } + for (i=0; i < scope.inventory_groups.length; i++) { + found = false; + for (j=0; j < scope.host_groups.length; j++) { + if (scope.inventory_groups[i].id === scope.host_groups[j].id) { + found = true; + } + } + if (!found) { + scope.available_groups.push(scope.inventory_groups[i]); + } + } + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get current groups for host: ' + host_id + '. GET returned: ' + status }); + }); + + if (scope.removeHostsReload) { + scope.removeHostsReload(); + } + scope.removeHostsReload = scope.$on('hostsReload', function() { + HostsReload(params); + }); + + if (!scope.$$phase) { + scope.$digest(); + } + }; + }]); diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 180b572452..3603302f45 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -38,829 +38,710 @@ */ +export default + angular.module('JobDetailHelper', ['Utilities', 'RestServices', 'ModalDialog']) -angular.module('JobDetailHelper', ['Utilities', 'RestServices', 'ModalDialog']) + .factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', + 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', 'AddNewTask', 'AddNewPlay', + function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, GetElapsed, + UpdateTaskStatus, DrawGraph, LoadHostSummary, JobIsFinished, AddNewTask, AddNewPlay) { + return function(params) { -.factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', - 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', 'AddNewTask', 'AddNewPlay', -function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, GetElapsed, - UpdateTaskStatus, DrawGraph, LoadHostSummary, JobIsFinished, AddNewTask, AddNewPlay) { - return function(params) { + var scope = params.scope, + event = params.event, + msg; - var scope = params.scope, - event = params.event, - msg; + $log.debug('processing event: ' + event.id); + $log.debug(event); - $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; + 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; } - return msg; - } - switch (event.event) { - case 'playbook_on_start': - if (!JobIsFinished(scope)) { - scope.job_status.started = event.created; - scope.job_status.status = 'running'; - } - break; + 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_play_start': + AddNewPlay({ scope: scope, event: event }); + break; - case 'playbook_on_setup': - AddNewTask({ 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 '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 '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 '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_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_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_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); + 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 : '' + }); } - 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 = 'active'; - } - } - }; -}]) - -.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 = 'active'; - } - } - }; -}]) - -.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', ['DrawGraph', 'UpdatePlayStatus', 'SetActivePlay', 'SetActiveTask', function(DrawGraph, 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: {} + .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'); }; + }]) - 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', function(GetElapsed, Empty, JobIsFinished) { - return function(params) { - var scope = params.scope, - failed = params.failed, - modified = params.modified, - started = params.started; - - 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 = modified; - } - if (!Empty(started) && Empty(scope.job_status.started)) { - scope.job_status.started = started; - } - if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) { - scope.job_status.elapsed = GetElapsed({ - start: scope.job_status.started, - end: scope.job_status.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'; + .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 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'; + 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); } } - 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, - h, host; - - /* - scope.host_summary.ok += (status === 'successful') ? 1 : 0; - scope.host_summary.changed += (status === 'changed') ? 1 : 0; - scope.host_summary.unreachable += (status === 'unreachable') ? 1 : 0; - scope.host_summary.failed += (status === 'failed') ? 1 : 0; - scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable + - scope.host_summary.failed; - */ - - if (scope.jobData.hostSummaries[name] !== undefined) { - scope.jobData.hostSummaries[name].ok += (status === 'successful') ? 1 : 0; - scope.jobData.hostSummaries[name].changed += (status === 'changed') ? 1 : 0; - scope.jobData.hostSummaries[name].unreachable += (status === 'unreachable') ? 1 : 0; - scope.jobData.hostSummaries[name].failed += (status === 'failed') ? 1 : 0; - if (status === 'failed' || status === 'unreachable') { - scope.jobData.hostSummaries[name].status = 'failed'; - } - } - else { - scope.jobData.hostSummaries[name] = { - id: name, - 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' - }; - } - - scope.host_summary.ok = 0; - scope.host_summary.changed = 0; - scope.host_summary.unreachable = 0; - scope.host_summary.failed = 0; - for (h in scope.jobData.hostSummaries) { - host = scope.jobData.hostSummaries[h]; - if (host.ok > 0 && host.failed === 0 && host.unreachable === 0 && host.changed === 0) { - scope.host_summary.ok++; - } - if (host.changed > 0 && host.failed === 0 && host.unreachable === 0) { - scope.host_summary.changed++; - } - if (host.failed > 0) { - scope.host_summary.failed++; - } - if (host.unreachable > 0) { - scope.host_summary.unreachable++; - } - } - scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable + - scope.host_summary.failed; - - 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 (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 = 'active'; } } - } + }; + }]) - if (play_id) { - scope.jobData.plays[play_id].tasks[task_id].hostResults[event_id] = { - id: event_id, + .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 = 'active'; + } + } + }; + }]) + + .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, - host_id: host_id, - task_id: task_id, - name: name, - created: created, - counter: counter, - msg: msg, - item: item + elapsed: '00:00:00', + hostCount: 0, + taskCount: 0, + fistTask: null, + unreachableCount: 0, + status_tip: "Event ID: " + event.id + "
Status: " + status_text, + tasks: {} }; - // increment the unreachable count on the play - if (status === 'unreachable') { - scope.jobData.plays[play_id].unreachableCount++; + SetActivePlay({ scope: scope }); + }; + }]) + + .factory('AddNewTask', ['DrawGraph', 'UpdatePlayStatus', 'SetActivePlay', 'SetActiveTask', function(DrawGraph, 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; } - - // 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 - }); + scope.jobData.plays[event.parent].taskCount++; 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); - 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', 'width': task.successfulPct + "%" } : { 'display': 'none' }; - task.changedStyle = (task.changedPct > 0) ? { 'display': 'inline-block', 'width': task.changedPct + "%" } : { 'display': 'none' }; - task.skippedStyle = (task.skippedPct > 0) ? { 'display': 'inline-block', 'width': task.skippedPct + "%" } : { 'display': 'none' }; - task.failedStyle = (task.failedPct > 0) ? { 'display': 'inline-block', 'width': task.failedPct + "%" } : { 'display': 'none' }; - task.unreachableStyle = (task.unreachablePct > 0) ? { 'display': 'inline-block', 'width': task.unreachablePct + "%" } : { 'display': 'none' }; - task.missingStyle = (task.missingPct > 0) ? { 'display': 'inline-block', 'width': task.missingPct + "%" } : { '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 = []; - - url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id'; - url += (scope.search_play_name) ? '&play__icontains=' + scope.search_play_name : ''; - url += (scope.search_play_status === 'failed') ? '&failed=true' : ''; - 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 }); + UpdatePlayStatus({ + scope: scope, + play_id: event.parent, + failed: event.failed, + changed: event.changed, + modified: event.modified }); - }; -}]) + }; + }]) -// 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; + .factory('UpdateJobStatus', ['GetElapsed', 'Empty', 'JobIsFinished', function(GetElapsed, Empty, JobIsFinished) { + return function(params) { + var scope = params.scope, + failed = params.failed, + modified = params.modified, + started = params.started; - scope.selectedPlay = id; - scope.plays.forEach(function(play, idx) { - if (play.id === scope.selectedPlay) { - scope.plays[idx].playActiveClass = 'active'; + 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 = modified; + } + if (!Empty(started) && Empty(scope.job_status.started)) { + scope.job_status.started = started; + } + if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) { + scope.job_status.elapsed = GetElapsed({ + start: scope.job_status.started, + end: scope.job_status.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, + h, host; + + /* + scope.host_summary.ok += (status === 'successful') ? 1 : 0; + scope.host_summary.changed += (status === 'changed') ? 1 : 0; + scope.host_summary.unreachable += (status === 'unreachable') ? 1 : 0; + scope.host_summary.failed += (status === 'failed') ? 1 : 0; + scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable + + scope.host_summary.failed; + */ + + if (scope.jobData.hostSummaries[name] !== undefined) { + scope.jobData.hostSummaries[name].ok += (status === 'successful') ? 1 : 0; + scope.jobData.hostSummaries[name].changed += (status === 'changed') ? 1 : 0; + scope.jobData.hostSummaries[name].unreachable += (status === 'unreachable') ? 1 : 0; + scope.jobData.hostSummaries[name].failed += (status === 'failed') ? 1 : 0; + if (status === 'failed' || status === 'unreachable') { + scope.jobData.hostSummaries[name].status = 'failed'; + } } else { - scope.plays[idx].playActiveClass = ''; + scope.jobData.hostSummaries[name] = { + id: name, + 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' + }; } - }); - 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; - url += (scope.search_task_name) ? '&task__icontains=' + 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]; - return false; + scope.host_summary.ok = 0; + scope.host_summary.changed = 0; + scope.host_summary.unreachable = 0; + scope.host_summary.failed = 0; + for (h in scope.jobData.hostSummaries) { + host = scope.jobData.hostSummaries[h]; + if (host.ok > 0 && host.failed === 0 && host.unreachable === 0 && host.changed === 0) { + scope.host_summary.ok++; } - return true; + if (host.changed > 0 && host.failed === 0 && host.unreachable === 0) { + scope.host_summary.changed++; + } + if (host.failed > 0) { + scope.host_summary.failed++; + } + if (host.unreachable > 0) { + scope.host_summary.unreachable++; + } + } + scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable + + scope.host_summary.failed; + + UpdateTaskStatus({ + scope: scope, + task_id: task_id, + failed: ((status === 'failed' || status === 'unreachable') ? true :false), + changed: ((status === 'changed') ? true : false), + modified: modified }); - scope.tasksLoading = true; + 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); + 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', 'width': task.successfulPct + "%" } : { 'display': 'none' }; + task.changedStyle = (task.changedPct > 0) ? { 'display': 'inline-block', 'width': task.changedPct + "%" } : { 'display': 'none' }; + task.skippedStyle = (task.skippedPct > 0) ? { 'display': 'inline-block', 'width': task.skippedPct + "%" } : { 'display': 'none' }; + task.failedStyle = (task.failedPct > 0) ? { 'display': 'inline-block', 'width': task.failedPct + "%" } : { 'display': 'none' }; + task.unreachableStyle = (task.unreachablePct > 0) ? { 'display': 'inline-block', 'width': task.unreachablePct + "%" } : { 'display': 'none' }; + task.missingStyle = (task.missingPct > 0) ? { 'display': 'inline-block', 'width': task.missingPct + "%" } : { '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 = []; + + url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id'; + url += (scope.search_play_name) ? '&play__icontains=' + scope.search_play_name : ''; + url += (scope.search_play_status === 'failed') ? '&failed=true' : ''; + scope.playsLoading = true; Rest.setUrl(url); Rest.get() .success(function(data) { - scope.next_tasks = data.next; - scope.tasks = []; + scope.next_plays = data.next; + scope.plays = []; data.results.forEach(function(event, idx) { - var end, elapsed, status, status_text; + var status, status_text, start, end, elapsed; - if (play.firstTask === undefined || play.firstTask === null) { - play.firstTask = event.id; - play.hostCount = (event.host_count) ? event.host_count : 0; - } + 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].created; + end = data.results[idx + 1].started; } - 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; - }); + else if (JobIsFinished(scope)) { + // this is the last play and the job already finished + end = scope.job_status.finished; } - if (end) { elapsed = GetElapsed({ - start: event.created, + start: start, end: end }); } @@ -868,172 +749,350 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge elapsed = '00:00:00'; } - status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; - status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK'; - - scope.tasks.push({ + scope.plays.push({ id: event.id, - play_id: scope.selectedPlay, - name: event.name, + name: event.play, + created: start, + finished: end, 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, + hostCount: 0, + fistTask: null, + playActiveClass: '', 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({ + SelectPlay({ scope: scope, - id: (scope.tasks.length > 0) ? scope.tasks[0].id : null, + id: (scope.plays.length > 0) ? scope.plays[0].id : null, callback: callback }); - - scope.tasksLoading = false; - + scope.playsLoading = 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 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 = 'active'; + } + else { + scope.plays[idx].playActiveClass = ''; + } }); - } - }; -}]) -// Call when the selected task needs to change -.factory('SelectTask', ['LoadHosts', function(LoadHosts) { - return function(params) { - var scope = params.scope, - id = params.id, - callback = params.callback; + LoadTasks({ + scope: scope, + callback: callback, + clear: true + }); - scope.selectedTask = id; - scope.tasks.forEach(function(task, idx) { - if (task.id === scope.selectedTask) { - scope.tasks[idx].taskActiveClass = 'active'; + }; + }]) + + .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; + url += (scope.search_task_name) ? '&task__icontains=' + 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]; + 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 { - scope.tasks[idx].taskActiveClass = ''; + SelectTask({ + scope: scope, + id: null, + callback: callback + }); } - }); + }; + }]) - LoadHosts({ - scope: scope, - callback: callback, - clear: true - }); - }; -}]) + // Call when the selected task needs to change + .factory('SelectTask', ['LoadHosts', function(LoadHosts) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback; -// Refresh the list of hosts -.factory('LoadHosts', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { - return function(params) { - var scope = params.scope, - callback = params.callback, - url; + scope.selectedTask = id; + scope.tasks.forEach(function(task, idx) { + if (task.id === scope.selectedTask) { + scope.tasks[idx].taskActiveClass = 'active'; + } + else { + scope.tasks[idx].taskActiveClass = ''; + } + }); - scope.hostResults = []; + LoadHosts({ + scope: scope, + callback: callback, + clear: true + }); + }; + }]) + + // Refresh the list of hosts + .factory('LoadHosts', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { + return function(params) { + var scope = params.scope, + callback = params.callback, + url; + + scope.hostResults = []; + + if (scope.selectedTask) { + // If we have a selected task, then get the list of hosts + url = scope.job.related.job_events + '?parent=' + scope.selectedTask + '&'; + url += (scope.search_host_name) ? 'host__name__icontains=' + scope.search_host_name + '&' : ''; + url += (scope.search_host_status === 'failed') ? 'failed=true&' : ''; + url += 'event__startswith=runner&page_size=' + scope.hostResultsMaxRows + '&order=host_name,counter'; + scope.hostResultsLoading = true; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + scope.next_host_results = data.next; + scope.hostResults = []; + data.results.forEach(function(event) { + var status, status_text, item, msg; + if (event.event === "runner_on_skipped") { + status = 'skipped'; + } + else if (event.event === "runner_on_unreachable") { + status = 'unreachable'; + } + else { + status = (event.failed) ? 'failed' : (event.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 (event.event_data && event.event_data.res) { + item = event.event_data.res.item; + if (typeof item === "object") { + item = JSON.stringify(item); + item = item.replace(/\"/g,'').replace(/:/g,': ').replace(/,/g,', '); + } + } + + 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; + } + } + + if (event.event !== "runner_on_no_hosts") { + scope.hostResults.push({ + id: event.id, + status: status, + status_text: status_text, + host_id: event.host, + task_id: event.parent, + name: event.event_data.host, + created: event.created, + msg: msg, + item: item + }); + } + }); + + scope.hostResultsLoading = false; + + if (callback) { + scope.$emit(callback); + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + if (callback) { + scope.$emit(callback); + } + //$('#hosts-table-detail').mCustomScrollbar("update"); + } + }; + }]) + + // Refresh the list of hosts in the hosts summary section + .factory('ReloadHostSummaryList', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { + return function(params) { + var scope = params.scope, + callback = params.callback, + url; + + url = scope.job.related.job_host_summaries + '?'; + url += (scope.search_host_summary_name) ? 'host_name__icontains=' + scope.search_host_summary_name + '&': ''; + url += (scope.search_host_summary_status === 'failed') ? 'failed=true&' : ''; + url += '&page_size=' + scope.hostSummariesMaxRows + '&order=host_name'; + + scope.hosts = []; + scope.hostSummariesLoading = true; - if (scope.selectedTask) { - // If we have a selected task, then get the list of hosts - url = scope.job.related.job_events + '?parent=' + scope.selectedTask + '&'; - url += (scope.search_host_name) ? 'host__name__icontains=' + scope.search_host_name + '&' : ''; - url += (scope.search_host_status === 'failed') ? 'failed=true&' : ''; - url += 'event__startswith=runner&page_size=' + scope.hostResultsMaxRows + '&order=host_name,counter'; - scope.hostResultsLoading = true; Rest.setUrl(url); Rest.get() .success(function(data) { - scope.next_host_results = data.next; - scope.hostResults = []; + scope.next_host_summaries = data.next; + scope.hosts = []; data.results.forEach(function(event) { - var status, status_text, item, msg; - if (event.event === "runner_on_skipped") { - status = 'skipped'; - } - else if (event.event === "runner_on_unreachable") { - status = 'unreachable'; + var name; + if (event.host_name) { + name = event.host_name; } else { - status = (event.failed) ? 'failed' : (event.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 (event.event_data && event.event_data.res) { - item = event.event_data.res.item; - if (typeof item === "object") { - item = JSON.stringify(item); - item = item.replace(/\"/g,'').replace(/:/g,': ').replace(/,/g,', '); - } - } - - 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; - } - } - - if (event.event !== "runner_on_no_hosts") { - scope.hostResults.push({ - id: event.id, - status: status, - status_text: status_text, - host_id: event.host, - task_id: event.parent, - name: event.event_data.host, - created: event.created, - msg: msg, - item: item - }); + name = ""; } + scope.hosts.push({ + id: name, + name: event.host_name, + ok: event.ok, + changed: event.changed, + unreachable: event.dark, + failed: event.failures, + status: (event.failed) ? 'failed' : 'successful' + }); }); - scope.hostResultsLoading = false; + scope.hostSummariesLoading = false; if (callback) { scope.$emit(callback); @@ -1043,266 +1102,130 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + '. GET returned: ' + status }); }); - } - else { - if (callback) { - scope.$emit(callback); + }; + }]) + + .factory('LoadHostSummary', [ function() { + return function(params) { + var scope = params.scope, + data = params.data, + host; + scope.host_summary.ok = 0; + for (host in data.ok) { + if (!data.changed[host] && !data.dark[host] && !data.failures[host]) { + scope.host_summary.ok += 1; + } } - //$('#hosts-table-detail').mCustomScrollbar("update"); - } - }; -}]) + scope.host_summary.changed = 0; + for (host in data.changed) { + if (!data.dark[host] && !data.failures[host]) { + scope.host_summary.changed += 1; + } + } + scope.host_summary.unreachable = 0; + for (host in data.dark) { + scope.host_summary.unreachable += 1; + } + scope.host_summary.failed = 0; + for (host in data.failures) { + scope.host_summary.failed += 1; + } + scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + + scope.host_summary.unreachable + scope.host_summary.failed; + }; + }]) -// Refresh the list of hosts in the hosts summary section -.factory('ReloadHostSummaryList', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { - return function(params) { - var scope = params.scope, - callback = params.callback, - url; + .factory('DrawGraph', [ function() { + return function(params) { + var scope = params.scope, + resize = params.resize, + width, height, svg_height, svg_width, svg_radius, svg, graph_data = []; - url = scope.job.related.job_host_summaries + '?'; - url += (scope.search_host_summary_name) ? 'host_name__icontains=' + scope.search_host_summary_name + '&': ''; - url += (scope.search_host_summary_status === 'failed') ? 'failed=true&' : ''; - url += '&page_size=' + scope.hostSummariesMaxRows + '&order=host_name'; - - scope.hosts = []; - scope.hostSummariesLoading = true; - - Rest.setUrl(url); - Rest.get() - .success(function(data) { - scope.next_host_summaries = data.next; - scope.hosts = []; - data.results.forEach(function(event) { - var name; - if (event.host_name) { - name = event.host_name; - } - else { - name = ""; - } - scope.hosts.push({ - id: name, - name: event.host_name, - ok: event.ok, - changed: event.changed, - unreachable: event.dark, - failed: event.failures, - status: (event.failed) ? 'failed' : 'successful' - }); + // Ready the data + if (scope.host_summary.ok) { + graph_data.push({ + label: 'OK', + value: (scope.host_summary.ok === scope.host_summary.total) ? 1 : scope.host_summary.ok, + color: '#00aa00' }); + } + if (scope.host_summary.changed) { + graph_data.push({ + label: 'Changed', + value: (scope.host_summary.changed === scope.host_summary.total) ? 1 : scope.host_summary.changed, + color: '#FF9900' + }); + } + if (scope.host_summary.unreachable) { + graph_data.push({ + label: 'Unreachable', + value: (scope.host_summary.unreachable === scope.host_summary.total) ? 1 : scope.host_summary.unreachable, + color: '#FF0000' + }); + } + if (scope.host_summary.failed) { + graph_data.push({ + label: 'Failed', + value: (scope.host_summary.failed === scope.host_summary.total) ? 1 : scope.host_summary.failed, + color: '#aa0000' + }); + } - scope.hostSummariesLoading = false; - - if (callback) { - scope.$emit(callback); + // Adjust the size + width = $('#job-summary-container .job_well').width(); + height = $('#job-summary-container .job_well').height() - $('#summary-well-top-section').height() - $('#graph-section .header').outerHeight() - 15; + svg_radius = Math.min(width, height); + svg_width = width; + svg_height = height; + if (svg_height > 0 && svg_width > 0) { + if (!resize && $('#graph-section svg').length > 0) { + Donut3D.transition("completedHostsDonut", graph_data, Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4); } - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }; -}]) - -.factory('LoadHostSummary', [ function() { - return function(params) { - var scope = params.scope, - data = params.data, - host; - scope.host_summary.ok = 0; - for (host in data.ok) { - if (!data.changed[host] && !data.dark[host] && !data.failures[host]) { - scope.host_summary.ok += 1; - } - } - scope.host_summary.changed = 0; - for (host in data.changed) { - if (!data.dark[host] && !data.failures[host]) { - scope.host_summary.changed += 1; - } - } - scope.host_summary.unreachable = 0; - for (host in data.dark) { - scope.host_summary.unreachable += 1; - } - scope.host_summary.failed = 0; - for (host in data.failures) { - scope.host_summary.failed += 1; - } - scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + - scope.host_summary.unreachable + scope.host_summary.failed; - }; -}]) - -.factory('DrawGraph', [ function() { - return function(params) { - var scope = params.scope, - resize = params.resize, - width, height, svg_height, svg_width, svg_radius, svg, graph_data = []; - - // Ready the data - if (scope.host_summary.ok) { - graph_data.push({ - label: 'OK', - value: (scope.host_summary.ok === scope.host_summary.total) ? 1 : scope.host_summary.ok, - color: '#00aa00' - }); - } - if (scope.host_summary.changed) { - graph_data.push({ - label: 'Changed', - value: (scope.host_summary.changed === scope.host_summary.total) ? 1 : scope.host_summary.changed, - color: '#FF9900' - }); - } - if (scope.host_summary.unreachable) { - graph_data.push({ - label: 'Unreachable', - value: (scope.host_summary.unreachable === scope.host_summary.total) ? 1 : scope.host_summary.unreachable, - color: '#FF0000' - }); - } - if (scope.host_summary.failed) { - graph_data.push({ - label: 'Failed', - value: (scope.host_summary.failed === scope.host_summary.total) ? 1 : scope.host_summary.failed, - color: '#aa0000' - }); - } - - // Adjust the size - width = $('#job-summary-container .job_well').width(); - height = $('#job-summary-container .job_well').height() - $('#summary-well-top-section').height() - $('#graph-section .header').outerHeight() - 15; - svg_radius = Math.min(width, height); - svg_width = width; - svg_height = height; - if (svg_height > 0 && svg_width > 0) { - if (!resize && $('#graph-section svg').length > 0) { - Donut3D.transition("completedHostsDonut", graph_data, Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4); - } - else { - if ($('#graph-section svg').length > 0) { - $('#graph-section svg').remove(); + else { + if ($('#graph-section svg').length > 0) { + $('#graph-section svg').remove(); + } + svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height); + svg.append("g").attr("id","completedHostsDonut"); + Donut3D.draw("completedHostsDonut", graph_data, Math.floor(svg_width / 2), Math.floor(svg_height / 2), Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4); + $('#graph-section .header .legend').show(); } - svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height); - svg.append("g").attr("id","completedHostsDonut"); - Donut3D.draw("completedHostsDonut", graph_data, Math.floor(svg_width / 2), Math.floor(svg_height / 2), Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4); - $('#graph-section .header .legend').show(); } - } - }; -}]) + }; + }]) -.factory('DrawPlays', [function() { - return function(params) { - var scope = params.scope, - idx = 0, - result = [], - newKeys = [], - plays = JSON.parse(JSON.stringify(scope.jobData.plays)), - filteredListX = [], - filteredListA = [], - filteredListB = [], - key, - keys; + .factory('DrawPlays', [function() { + return function(params) { + var scope = params.scope, + idx = 0, + result = [], + newKeys = [], + plays = JSON.parse(JSON.stringify(scope.jobData.plays)), + filteredListX = [], + filteredListA = [], + filteredListB = [], + key, + keys; - function listSort(a,b) { - if (parseInt(a,10) < parseInt(b,10)) { - return -1; + 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 (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]; - } - } - if (scope.search_play_name) { + // Only draw plays that are in the 'active' list for (key in plays) { - if (filteredListX[key].name.indexOf(scope.search_play_name) > 0) { - filteredListA[key] = filteredListX[key]; + if (plays[key].taskCount > 0) { + filteredListX[key] = plays[key]; } } - } - else { - filteredListA = filteredListX; - } - - 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++) { - 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 = [], - filteredListA = [], - 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)); - - // 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]; - } - } - - if (scope.search_task_name) { - for (key in filteredListX) { - if (filteredListX[key].name.indexOf(scope.search_task_name) > 0) { + if (scope.search_play_name) { + for (key in plays) { + if (filteredListX[key].name.indexOf(scope.search_play_name) > 0) { filteredListA[key] = filteredListX[key]; } } @@ -1311,10 +1234,10 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge filteredListA = filteredListX; } - if (scope.search_task_status === 'failed') { + if (scope.search_play_status === 'failed') { for (key in filteredListA) { if (filteredListA[key].status === 'failed') { - filteredListB[key] = tasks[key]; + filteredListB[key] = plays[key]; } } } @@ -1324,8 +1247,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge 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++) { + for (idx=0; idx < scope.playsMaxRows && idx < keys.length; idx++) { newKeys.push(keys[idx]); } newKeys.sort(function(a,b) { return listSort(a,b); }); @@ -1334,178 +1256,256 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge 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")); - } + 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 = [], + filteredListA = [], + filteredListB = [], + idx, key, keys, newKeys, tasks, t; -.factory('DrawHostResults', [ function() { - return function(params) { - var scope = params.scope, - result = [], - filteredListA = [], - filteredListB = [], - idx = 0, - hostResults, - 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; + } - if (scope.activePlay && scope.activeTask && scope.jobData.plays[scope.activePlay] && - scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) { + if (scope.activePlay && scope.jobData.plays[scope.activePlay]) { - hostResults = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults)); + tasks = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks)); - if (scope.search_host_name) { - for (key in hostResults) { - if (hostResults[key].name.indexOf(scope.search_host_name) > 0) { - filteredListA[key] = hostResults[key]; + // 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]; } } - } - else { - filteredListA = hostResults; - } - if (scope.search_host_status === 'failed' || scope.search_host_status === 'unreachable') { - for (key in filteredListA) { - if (filteredListA[key].status === 'failed') { - filteredListB[key] = filteredListA[key]; + 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; + } + + 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 = []; + 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++; + } } - else { - filteredListB = filteredListA; + + 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 = [], + filteredListA = [], + filteredListB = [], + idx = 0, + hostResults, + key, + keys; + + 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)); + + 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; + } + + 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) { + 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++; + } } - 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; + + setTimeout( function() { + scope.$apply( function() { + scope.hostResults = result; + if (scope.liveEventProcessing) { + $('#hosts-table-detail').scrollTop($('#hosts-table-detail').prop("scrollHeight")); } - if (filteredListB[a].counter >filteredListB[b].counter) { - return 1; + }); + }); + + }; + }]) + + .factory('DrawHostSummaries', [ function() { + return function(params) { + var scope = params.scope, + result = [], + filteredListA = [], + filteredListB = [], + idx = 0, + hostSummaries, + key, + keys = Object.keys(scope.jobData.hostSummaries); + if (keys.length > 0) { + hostSummaries = JSON.parse(JSON.stringify(scope.jobData.hostSummaries)); + if (scope.search_host_summary_name) { + for (key in hostSummaries) { + if (hostSummaries[key].name.indexOf(scope.search_host_summary_name) > 0) { + filteredListA[key] = hostSummaries[key]; + } } - } else { - if (filteredListB[a].name < filteredListB[b].name) { - return -1; + } + else { + filteredListA = hostSummaries; + } + + if (scope.search_host_summary_status === 'failed') { + for (key in filteredListA) { + if (filteredListA[key].status === 'failed' || filteredListA[key].status === 'unreachable') { + filteredListB[key] = filteredListA[key]; + } } + } + else { + filteredListB = filteredListA; + } + + keys = Object.keys(filteredListB); + + keys.sort(function(a,b) { 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('DrawHostSummaries', [ function() { - return function(params) { - var scope = params.scope, - result = [], - filteredListA = [], - filteredListB = [], - idx = 0, - hostSummaries, - key, - keys = Object.keys(scope.jobData.hostSummaries); - if (keys.length > 0) { - hostSummaries = JSON.parse(JSON.stringify(scope.jobData.hostSummaries)); - if (scope.search_host_summary_name) { - for (key in hostSummaries) { - if (hostSummaries[key].name.indexOf(scope.search_host_summary_name) > 0) { - filteredListA[key] = hostSummaries[key]; + if (filteredListB[a].name < filteredListB[b].name) { + return -1; } - } - } - else { - filteredListA = hostSummaries; - } + // a must be equal to b + return 0; + }); - if (scope.search_host_summary_status === 'failed') { - for (key in filteredListA) { - if (filteredListA[key].status === 'failed' || filteredListA[key].status === 'unreachable') { - filteredListB[key] = filteredListA[key]; - } + while (idx < keys.length && result.length < scope.hostSummariesMaxRows) { + result.push(filteredListB[keys[idx]]); + idx++; } } - else { - filteredListB = filteredListA; - } - - keys = Object.keys(filteredListB); - - keys.sort(function(a,b) { - 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; + setTimeout( function() { + scope.$apply( function() { + scope.hosts = result; + }); }); + }; + }]) - while (idx < keys.length && result.length < scope.hostSummariesMaxRows) { - result.push(filteredListB[keys[idx]]); - idx++; + .factory('UpdateDOM', ['DrawPlays', 'DrawTasks', 'DrawHostResults', 'DrawHostSummaries', 'DrawGraph', + function(DrawPlays, DrawTasks, DrawHostResults, DrawHostSummaries, DrawGraph) { + return function(params) { + var scope = params.scope; + + if (!scope.pauseLiveEvents) { + DrawPlays({ scope: scope }); + DrawTasks({ scope: scope }); + DrawHostResults({ scope: scope }); } - } - setTimeout( function() { - scope.$apply( function() { - scope.hosts = result; - }); - }); - }; -}]) -.factory('UpdateDOM', ['DrawPlays', 'DrawTasks', 'DrawHostResults', 'DrawHostSummaries', 'DrawGraph', - function(DrawPlays, DrawTasks, DrawHostResults, DrawHostSummaries, DrawGraph) { - return function(params) { - var scope = params.scope; + DrawHostSummaries({ scope: scope }); - if (!scope.pauseLiveEvents) { - DrawPlays({ scope: scope }); - DrawTasks({ scope: scope }); - DrawHostResults({ scope: scope }); - } + setTimeout(function() { + scope.playsLoading = false; + scope.tasksLoading = false; + scope.hostResultsLoading = false; + scope.LoadHostSummaries = false; + },100); - DrawHostSummaries({ scope: scope }); - - setTimeout(function() { - scope.playsLoading = false; - scope.tasksLoading = false; - scope.hostResultsLoading = false; - scope.LoadHostSummaries = false; - },100); - - if (scope.host_summary.total > 0) { - DrawGraph({ scope: scope, resize: true }); - } - }; -}]); + if (scope.host_summary.total > 0) { + DrawGraph({ scope: scope, resize: true }); + } + }; + }]); diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index 60ee289df1..0b08ba998e 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -4,1024 +4,1025 @@ * JobSubmission.js * */ - /** - * @ngdoc function - * @name helpers.function:JobSubmission - * @description +/** +* @ngdoc function +* @name helpers.function:JobSubmission +* @description */ +'use strict'; - +export default angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition', - 'LookUpHelper', 'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition']) - -.factory('LaunchJob', ['Rest', 'Wait', 'ProcessErrors', 'ToJSON', 'Empty', 'GetBasePath', - function(Rest, Wait, ProcessErrors, ToJSON, Empty, GetBasePath) { - return function(params) { - var scope = params.scope, - callback = params.callback || 'JobLaunched', - job_launch_data = {}, - url = params.url, - vars_url = GetBasePath('job_templates')+scope.job_template_id + '/', - // fld, - extra_vars; - - //found it easier to assume that there will be extra vars, and then check for a blank object at the end - job_launch_data.extra_vars = {}; - - //gather the extra vars from the job template if survey is enabled and prompt for vars is false - if (scope.removeGetExtraVars) { - scope.removeGetExtraVars(); - } - scope.removeGetExtraVars = scope.$on('GetExtraVars', function() { - - Rest.setUrl(vars_url); - Rest.get() - .success(function (data) { - if(!Empty(data.extra_vars)){ - data.extra_vars = ToJSON('json', data.extra_vars, false); - $.each(data.extra_vars, function(key,value){ - job_launch_data.extra_vars[key] = value; - }); - } - scope.$emit('BuildData'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, { hdr: 'Error!', - msg: 'Failed to retrieve job template extra variables.' }); - }); - }); - - //build the data object to be sent to the job launch endpoint. Any variables gathered from the survey and the extra variables text editor are inserted into the extra_vars dict of the job_launch_data - if (scope.removeBuildData) { - scope.removeBuildData(); - } - scope.removeBuildData = scope.$on('BuildData', function() { - if(!Empty(scope.passwords_needed_to_start) && scope.passwords_needed_to_start.length>0){ - scope.passwords.forEach(function(password) { - job_launch_data[password] = scope[password]; - scope.passwords_needed_to_start.push(password+'_confirm'); // i'm pushing these values into this array for use during the survey taker parsing - }); - } - if(scope.prompt_for_vars===true){ - extra_vars = ToJSON(scope.parseType, scope.extra_vars, false); - if(!Empty(extra_vars)){ - $.each(extra_vars, function(key,value){ - job_launch_data.extra_vars[key] = value; - }); - } - - } - if(scope.survey_enabled===true){ - for (var fld in scope.job_launch_form){ - //grab only survey question fields, including those that are zero or a blank answer (for optional questions) - if((scope[fld] || scope[fld] === 0 || scope[fld]==="") && scope.passwords_needed_to_start.indexOf(fld) === -1 && fld !== 'extra_vars'){ - job_launch_data.extra_vars[fld] = scope[fld]; - } - } - } - - // include the credential used if the user was prompted to choose a cred - if(!Empty(scope.credential)){ - job_launch_data.credential_id = scope.credential; - } - - // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. - if(jQuery.isEmptyObject(job_launch_data.extra_vars)===true && scope.prompt_for_vars===false){ - delete job_launch_data.extra_vars; - } - - Rest.setUrl(url); - Rest.post(job_launch_data) - .success(function(data) { - Wait('stop'); - if(!$('#password-modal').is(':hidden')){ - $('#password-modal').dialog('close'); - } - scope.$emit(callback, data); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status }); - }); - }); - - // if the user has a survey and does not have 'prompt for vars' selected, then we want to - // include the extra vars from the job template in the job launch. so first check for these conditions - // and then overlay any survey vars over those. - if(scope.prompt_for_vars===false && scope.survey_enabled===true){ - scope.$emit('GetExtraVars'); - } - else { - scope.$emit('BuildData'); - } - - - }; - }]) - -.factory('PromptForCredential', ['$location', 'Wait', 'GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList', 'Rest', 'Prompt', 'ProcessErrors', -function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Rest, Prompt, ProcessErrors) { - return function(params) { - - var scope = params.scope, - callback = params.callback || 'CredentialReady', - selectionMade; - - Wait('stop'); - scope.credential = ''; - - if (scope.removeShowLookupDialog) { - scope.removeShowLookupDialog(); - } - scope.removeShowLookupDialog = scope.$on('ShowLookupDialog', function() { - selectionMade = function () { - scope.$emit(callback, scope.credential); - }; - - LookUpInit({ - url: GetBasePath('credentials') + '?kind=ssh', - scope: scope, - form: JobTemplateForm(), - current_item: null, - list: CredentialList, - field: 'credential', - hdr: 'Credential Required', - instructions: "Launching this job requires a machine credential. Please select your machine credential now or Cancel to quit.", - postAction: selectionMade, - input_type: 'radio' - }); - scope.lookUpCredential(); - }); - - if (scope.removeAlertNoCredentials) { - scope.removeAlertNoCredentials(); - } - scope.removeAlertNoCredentials = scope.$on('AlertNoCredentials', function() { - var action = function () { - $('#prompt-modal').modal('hide'); - $location.url('/credentials/add'); - }; - - Prompt({ - hdr: 'Machine Credential Required', - body: "
There are no machine credentials defined in Tower. Launching this job requires a machine credential. " + - "Create one now?", - action: action - }); - }); - - Rest.setUrl(GetBasePath('credentials') + '?kind=ssh'); - Rest.get() - .success(function(data) { - if (data.results.length > 0) { - scope.$emit('ShowLookupDialog'); - } - else { - scope.$emit('AlertNoCredentials'); - } - }) - .error(function(data,status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Checking for machine credentials failed. GET returned: ' + status }); - }); - }; -}]) - - - -.factory('CreateLaunchDialog', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateDialog', 'GenerateForm', - 'JobVarsPromptForm', 'Wait', 'ParseTypeChange', - function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm, - JobVarsPromptForm, 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); - }, - 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); - // if(scope.prompt_for_vars===true){ - // setTimeout(function() { - // TextareaResize({ - // scope: scope, - // textareaId: 'job_variables', - // modalId: 'password-modal', - // formId: 'job_launch_form', - // parse: true - // }); - // }, 300); - // } - - }); - }; - -}]) - - - - -.factory('PromptForPasswords', ['$compile', 'Wait', 'Alert', 'CredentialForm', - function($compile, Wait, Alert, CredentialForm) { - return function(params) { - var scope = params.scope, - callback = params.callback || 'PasswordsAccepted', - url = params.url, - form = CredentialForm, - // acceptedPasswords = {}, - fld, field, - html=params.html || ""; - - scope.passwords = params.passwords; - // Wait('stop'); - - - html += "
Launching this job requires the passwords listed below. Enter and confirm each password before continuing.
\n"; - // html += "
\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"; - } - }); - // html += "\n"; - - - // $('#password-modal').empty().html(buildHtml); - // e = angular.element(document.getElementById('password-modal')); - // $compile(e)(scope); - scope.$emit(callback, html, url); - // CreateLaunchDialog({scope: scope}) - // buttons = [{ - // label: "Cancel", - // onClick: function() { - // scope.passwordCancel(); - // }, - // icon: "fa-times", - // "class": "btn btn-default", - // "id": "password-cancel-button" - // },{ - // label: "Continue", - // onClick: function() { - // scope.passwordAccept(); - // }, - // icon: "fa-check", - // "class": "btn btn-primary", - // "id": "password-accept-button" - // }]; - - - // CreateDialog({ - // id: 'password-modal', - // scope: scope, - // buttons: buttons, - // width: 600, - // height: (parent_scope.passwords.length > 1) ? 700 : 500, - // minWidth: 500, - // title: 'parent_scope.passwords Required', - // callback: 'DialogReady' - // }); - - // if (scope.removeDialogReady) { - // scope.removeDialogReady(); - // } - // scope.removeDialogReady = scope.$on('DialogReady', function() { - // $('#password-modal').dialog('open'); - // $('#password-accept-button').attr({ "disabled": "disabled" }); - // }); - // scope.keydown = function(e){ - // if(e.keyCode===13){ - // scope.passwordAccept(); - // } - // }; - - // scope.passwordAccept = function() { - // if (!scope.password_form.$invalid) { - // scope.passwords.forEach(function(password) { - // acceptedPasswords[password] = scope[password]; - // }); - // $('#password-modal').dialog('close'); - // scope.$emit(callback, acceptedPasswords); - // } - // }; - - // scope.passwordCancel = function() { - // $('#password-modal').dialog('close'); - // scope.$emit('CancelJob'); - // scope.$destroy(); - // }; - - // 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('PromptForVars', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateLaunchDialog', 'GenerateForm', 'JobVarsPromptForm', 'Wait', - 'ParseVariableString', 'ToJSON', 'ProcessErrors', '$routeParams' , - function($compile, Rest, GetBasePath, TextareaResize,CreateLaunchDialog, GenerateForm, JobVarsPromptForm, Wait, - ParseVariableString, ToJSON, ProcessErrors, $routeParams) { - return function(params) { - var - // parent_scope = params.scope, - scope = params.scope, - callback = params.callback, - // job = params.job, - url = params.url, - vars_url = GetBasePath('job_templates')+scope.job_template_id + '/', - html = params.html || ""; - - - function buildHtml(extra_vars){ - - html += GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); - html = html.replace("", ""); - scope.helpContainer = "\n"; - - scope.helpText = "

After defining any extra variables, click Continue to start the job. Otherwise, click cancel to abort.

" + - "

Extra variables are passed as command line variables to the playbook run. It is equivalent to the -e or --extra-vars " + - "command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON.

" + - "JSON:
\n" + - "
{
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n"; - - scope.extra_vars = ParseVariableString(extra_vars); - scope.parseType = 'yaml'; - scope.$emit(callback, html, url); - } - - Rest.setUrl(vars_url); - Rest.get() - .success(function (data) { - buildHtml(data.extra_vars); - - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, { hdr: 'Error!', - msg: 'Failed to retrieve organization: ' + $routeParams.id + '. GET status: ' + status }); - }); - - }; -}]) - -.factory('PromptForSurvey', ['$compile', 'Wait', 'Alert', 'CredentialForm', 'CreateLaunchDialog', 'SurveyControllerInit' , 'GetBasePath', 'Rest' , 'Empty', - 'GenerateForm', 'ShowSurveyModal', 'ProcessErrors', '$routeParams' , - function($compile, Wait, Alert, CredentialForm, CreateLaunchDialog, SurveyControllerInit, GetBasePath, Rest, Empty, - GenerateForm, ShowSurveyModal, ProcessErrors, $routeParams) { - return function(params) { - var html = params.html || "", - id= params.id, - url = params.url, - callback=params.callback, - scope = params.scope, - i, j, - requiredAsterisk, - requiredClasses, - defaultValue, - choices, - element, - minlength, maxlength, - checked, min, max, - survey_url = GetBasePath('job_templates') + id + '/survey_spec/' ; - - function buildHtml(question, index){ - question.index = index; - question.question_name = question.question_name.replace(//g, ">"); - question.question_description = (question.question_description) ? question.question_description.replace(//g, ">") : undefined; - - - requiredAsterisk = (question.required===true) ? "prepend-asterisk" : ""; - requiredClasses = (question.required===true) ? "ng-pristine ng-invalid-required ng-invalid" : ""; - - html+='
'; - html += '\n'; - - if(!Empty(question.question_description)){ - html += '
'+question.question_description+'
\n'; - } - - // if(question.default && question.default.indexOf('<') !== -1){ - // question.default = (question.default) ? question.default.replace(/') !== -1){ - // question.default = (question.default) ? question.default.replace(/>/g, ">") : undefined; - // } - scope[question.variable] = question.default; - - if(question.type === 'text' ){ - minlength = (!Empty(question.min)) ? Number(question.min) : ""; - maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ - '
'; - } - - if(question.type === "textarea"){ - scope[question.variable] = question.default || question.default_textarea; - minlength = (!Empty(question.min)) ? Number(question.min) : ""; - maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ - '
'; - } - - if(question.type === 'multiplechoice'){ - choices = question.choices.split(/\n/); - element = (question.type==="multiselect") ? "checkbox" : 'radio'; - question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; - html+='
'; - for( j = 0; j/g, ">"); - html+= '' + - ''+choices[j] +'
' ; - } - html+= '
Please select an answer.
'+ - '
'; - html+= '
'; //end survey_taker_input - } - - if(question.type === "multiselect"){ - //seperate the choices out into an array - choices = question.choices.split(/\n/); - question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; - //ensure that the default answers are in an array - scope[question.variable] = question.default.split(/\n/); - //create a new object to be used by the surveyCheckboxes directive - scope[question.variable + '_object'] = { - name: question.variable, - value: (question.default.split(/\n/)[0]==="") ? [] : question.default.split(/\n/) , - required: question.required, - options:[] - }; - //load the options into the 'options' key of the new object - for(j=0; j'+ - '{{job_launch_form.'+question.variable+'_object.$error.checkbox}}'+ - '
Please select at least one answer.
'; - } - - if(question.type === 'integer'){ - min = (!Empty(question.min)) ? Number(question.min) : ""; - max = (!Empty(question.max)) ? Number(question.max) : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer that is a valid integer.
'+ - '
Please enter an answer between {{'+min+'}} and {{'+max+'}}.
'; - - } - - if(question.type === "float"){ - min = (!Empty(question.min)) ? question.min : ""; - max = (!Empty(question.max)) ? question.max : "" ; - defaultValue = (!Empty(question.default)) ? question.default : (!Empty(question.default_float)) ? question.default_float : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer that is a decimal number.
'+ - '
Please enter a decimal number between {{'+min+'}} and {{'+max+'}}.
'; - } - html+='
'; - if(question.index === scope.survey_questions.length-1){ - scope.$emit(callback, html, url); - } - } - - - - - Rest.setUrl(survey_url); - Rest.get() - .success(function (data) { - if(!Empty(data)){ - scope.survey_name = data.name; - scope.survey_description = data.description; - scope.survey_questions = data.spec; - - for(i=0; i0){ + scope.passwords.forEach(function(password) { + job_launch_data[password] = scope[password]; + scope.passwords_needed_to_start.push(password+'_confirm'); // i'm pushing these values into this array for use during the survey taker parsing + }); + } + if(scope.prompt_for_vars===true){ + extra_vars = ToJSON(scope.parseType, scope.extra_vars, false); + if(!Empty(extra_vars)){ + $.each(extra_vars, function(key,value){ + job_launch_data.extra_vars[key] = value; + }); + } + + } + if(scope.survey_enabled===true){ + for (var fld in scope.job_launch_form){ + //grab only survey question fields, including those that are zero or a blank answer (for optional questions) + if((scope[fld] || scope[fld] === 0 || scope[fld]==="") && scope.passwords_needed_to_start.indexOf(fld) === -1 && fld !== 'extra_vars'){ + job_launch_data.extra_vars[fld] = scope[fld]; + } + } + } + + // include the credential used if the user was prompted to choose a cred + if(!Empty(scope.credential)){ + job_launch_data.credential_id = scope.credential; + } + + // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. + if(jQuery.isEmptyObject(job_launch_data.extra_vars)===true && scope.prompt_for_vars===false){ + delete job_launch_data.extra_vars; + } + + Rest.setUrl(url); + Rest.post(job_launch_data) + .success(function(data) { + Wait('stop'); + if(!$('#password-modal').is(':hidden')){ + $('#password-modal').dialog('close'); + } + scope.$emit(callback, data); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status }); + }); + }); + + // if the user has a survey and does not have 'prompt for vars' selected, then we want to + // include the extra vars from the job template in the job launch. so first check for these conditions + // and then overlay any survey vars over those. + if(scope.prompt_for_vars===false && scope.survey_enabled===true){ + scope.$emit('GetExtraVars'); + } + else { + scope.$emit('BuildData'); + } + + + }; + }]) + + .factory('PromptForCredential', ['$location', 'Wait', 'GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList', 'Rest', 'Prompt', 'ProcessErrors', + function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Rest, Prompt, ProcessErrors) { + return function(params) { + + var scope = params.scope, + callback = params.callback || 'CredentialReady', + selectionMade; + + Wait('stop'); + scope.credential = ''; + + if (scope.removeShowLookupDialog) { + scope.removeShowLookupDialog(); + } + scope.removeShowLookupDialog = scope.$on('ShowLookupDialog', function() { + selectionMade = function () { + scope.$emit(callback, scope.credential); + }; + + LookUpInit({ + url: GetBasePath('credentials') + '?kind=ssh', + scope: scope, + form: JobTemplateForm(), + current_item: null, + list: CredentialList, + field: 'credential', + hdr: 'Credential Required', + instructions: "Launching this job requires a machine credential. Please select your machine credential now or Cancel to quit.", + postAction: selectionMade, + input_type: 'radio' + }); + scope.lookUpCredential(); + }); + + if (scope.removeAlertNoCredentials) { + scope.removeAlertNoCredentials(); + } + scope.removeAlertNoCredentials = scope.$on('AlertNoCredentials', function() { + var action = function () { + $('#prompt-modal').modal('hide'); + $location.url('/credentials/add'); + }; + + Prompt({ + hdr: 'Machine Credential Required', + body: "
There are no machine credentials defined in Tower. Launching this job requires a machine credential. " + + "Create one now?", + action: action + }); + }); + + Rest.setUrl(GetBasePath('credentials') + '?kind=ssh'); + Rest.get() + .success(function(data) { + if (data.results.length > 0) { + scope.$emit('ShowLookupDialog'); + } + else { + scope.$emit('AlertNoCredentials'); + } + }) + .error(function(data,status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Checking for machine credentials failed. GET returned: ' + status }); + }); + }; + }]) + + + + .factory('CreateLaunchDialog', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateDialog', 'GenerateForm', + 'JobVarsPromptForm', 'Wait', 'ParseTypeChange', + function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm, + JobVarsPromptForm, 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); + }, + 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); + // if(scope.prompt_for_vars===true){ + // setTimeout(function() { + // TextareaResize({ + // scope: scope, + // textareaId: 'job_variables', + // modalId: 'password-modal', + // formId: 'job_launch_form', + // parse: true + // }); + // }, 300); + // } + + }); + }; + + }]) + + + + + .factory('PromptForPasswords', ['$compile', 'Wait', 'Alert', 'CredentialForm', + function($compile, Wait, Alert, CredentialForm) { + return function(params) { + var scope = params.scope, + callback = params.callback || 'PasswordsAccepted', + url = params.url, + form = CredentialForm, + // acceptedPasswords = {}, + fld, field, + html=params.html || ""; + + scope.passwords = params.passwords; + // Wait('stop'); + + + html += "
Launching this job requires the passwords listed below. Enter and confirm each password before continuing.
\n"; + // html += "
\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"; + } + }); + // html += "\n"; + + + // $('#password-modal').empty().html(buildHtml); + // e = angular.element(document.getElementById('password-modal')); + // $compile(e)(scope); + scope.$emit(callback, html, url); + // CreateLaunchDialog({scope: scope}) + // buttons = [{ + // label: "Cancel", + // onClick: function() { + // scope.passwordCancel(); + // }, + // icon: "fa-times", + // "class": "btn btn-default", + // "id": "password-cancel-button" + // },{ + // label: "Continue", + // onClick: function() { + // scope.passwordAccept(); + // }, + // icon: "fa-check", + // "class": "btn btn-primary", + // "id": "password-accept-button" + // }]; + + + // CreateDialog({ + // id: 'password-modal', + // scope: scope, + // buttons: buttons, + // width: 600, + // height: (parent_scope.passwords.length > 1) ? 700 : 500, + // minWidth: 500, + // title: 'parent_scope.passwords Required', + // callback: 'DialogReady' + // }); + + // if (scope.removeDialogReady) { + // scope.removeDialogReady(); + // } + // scope.removeDialogReady = scope.$on('DialogReady', function() { + // $('#password-modal').dialog('open'); + // $('#password-accept-button').attr({ "disabled": "disabled" }); + // }); + // scope.keydown = function(e){ + // if(e.keyCode===13){ + // scope.passwordAccept(); + // } + // }; + + // scope.passwordAccept = function() { + // if (!scope.password_form.$invalid) { + // scope.passwords.forEach(function(password) { + // acceptedPasswords[password] = scope[password]; + // }); + // $('#password-modal').dialog('close'); + // scope.$emit(callback, acceptedPasswords); + // } + // }; + + // scope.passwordCancel = function() { + // $('#password-modal').dialog('close'); + // scope.$emit('CancelJob'); + // scope.$destroy(); + // }; + + // 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('PromptForVars', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateLaunchDialog', 'GenerateForm', 'JobVarsPromptForm', 'Wait', + 'ParseVariableString', 'ToJSON', 'ProcessErrors', '$routeParams' , + function($compile, Rest, GetBasePath, TextareaResize,CreateLaunchDialog, GenerateForm, JobVarsPromptForm, Wait, + ParseVariableString, ToJSON, ProcessErrors, $routeParams) { + return function(params) { + var + // parent_scope = params.scope, + scope = params.scope, + callback = params.callback, + // job = params.job, + url = params.url, + vars_url = GetBasePath('job_templates')+scope.job_template_id + '/', + html = params.html || ""; + + + function buildHtml(extra_vars){ + + html += GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); + html = html.replace("", ""); + scope.helpContainer = "\n"; + + scope.helpText = "

After defining any extra variables, click Continue to start the job. Otherwise, click cancel to abort.

" + + "

Extra variables are passed as command line variables to the playbook run. It is equivalent to the -e or --extra-vars " + + "command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON.

" + + "JSON:
\n" + + "
{
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" + + "YAML:
\n" + + "
---
somevar: somevalue
password: magic
\n"; + + scope.extra_vars = ParseVariableString(extra_vars); + scope.parseType = 'yaml'; + scope.$emit(callback, html, url); + } + + Rest.setUrl(vars_url); + Rest.get() + .success(function (data) { + buildHtml(data.extra_vars); + + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, { hdr: 'Error!', + msg: 'Failed to retrieve organization: ' + $routeParams.id + '. GET status: ' + status }); + }); + + }; + }]) + + .factory('PromptForSurvey', ['$compile', 'Wait', 'Alert', 'CredentialForm', 'CreateLaunchDialog', 'SurveyControllerInit' , 'GetBasePath', 'Rest' , 'Empty', + 'GenerateForm', 'ShowSurveyModal', 'ProcessErrors', '$routeParams' , + function($compile, Wait, Alert, CredentialForm, CreateLaunchDialog, SurveyControllerInit, GetBasePath, Rest, Empty, + GenerateForm, ShowSurveyModal, ProcessErrors, $routeParams) { + return function(params) { + var html = params.html || "", + id= params.id, + url = params.url, + callback=params.callback, + scope = params.scope, + i, j, + requiredAsterisk, + requiredClasses, + defaultValue, + choices, + element, + minlength, maxlength, + checked, min, max, + survey_url = GetBasePath('job_templates') + id + '/survey_spec/' ; + + function buildHtml(question, index){ + question.index = index; + question.question_name = question.question_name.replace(//g, ">"); + question.question_description = (question.question_description) ? question.question_description.replace(//g, ">") : undefined; + + + requiredAsterisk = (question.required===true) ? "prepend-asterisk" : ""; + requiredClasses = (question.required===true) ? "ng-pristine ng-invalid-required ng-invalid" : ""; + + html+='
'; + html += '\n'; + + if(!Empty(question.question_description)){ + html += '
'+question.question_description+'
\n'; + } + + // if(question.default && question.default.indexOf('<') !== -1){ + // question.default = (question.default) ? question.default.replace(/') !== -1){ + // question.default = (question.default) ? question.default.replace(/>/g, ">") : undefined; + // } + scope[question.variable] = question.default; + + if(question.type === 'text' ){ + minlength = (!Empty(question.min)) ? Number(question.min) : ""; + maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ + '
'; + } + + if(question.type === "textarea"){ + scope[question.variable] = question.default || question.default_textarea; + minlength = (!Empty(question.min)) ? Number(question.min) : ""; + maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ + '
'; + } + + if(question.type === 'multiplechoice'){ + choices = question.choices.split(/\n/); + element = (question.type==="multiselect") ? "checkbox" : 'radio'; + question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; + html+='
'; + for( j = 0; j/g, ">"); + html+= '' + + ''+choices[j] +'
' ; + } + html+= '
Please select an answer.
'+ + '
'; + html+= '
'; //end survey_taker_input + } + + if(question.type === "multiselect"){ + //seperate the choices out into an array + choices = question.choices.split(/\n/); + question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; + //ensure that the default answers are in an array + scope[question.variable] = question.default.split(/\n/); + //create a new object to be used by the surveyCheckboxes directive + scope[question.variable + '_object'] = { + name: question.variable, + value: (question.default.split(/\n/)[0]==="") ? [] : question.default.split(/\n/) , + required: question.required, + options:[] + }; + //load the options into the 'options' key of the new object + for(j=0; j'+ + '{{job_launch_form.'+question.variable+'_object.$error.checkbox}}'+ + '
Please select at least one answer.
'; + } + + if(question.type === 'integer'){ + min = (!Empty(question.min)) ? Number(question.min) : ""; + max = (!Empty(question.max)) ? Number(question.max) : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer that is a valid integer.
'+ + '
Please enter an answer between {{'+min+'}} and {{'+max+'}}.
'; + + } + + if(question.type === "float"){ + min = (!Empty(question.min)) ? question.min : ""; + max = (!Empty(question.max)) ? question.max : "" ; + defaultValue = (!Empty(question.default)) ? question.default : (!Empty(question.default_float)) ? question.default_float : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer that is a decimal number.
'+ + '
Please enter a decimal number between {{'+min+'}} and {{'+max+'}}.
'; + } + html+='
'; + if(question.index === scope.survey_questions.length-1){ + scope.$emit(callback, html, url); + } + } + + + + + Rest.setUrl(survey_url); + Rest.get() + .success(function (data) { + if(!Empty(data)){ + scope.survey_name = data.name; + scope.survey_description = data.description; + scope.survey_questions = data.spec; + + for(i=0; i0){ - scope.passwords_needed_to_start = passwords; - scope.$emit('PromptForPasswords', passwords, html, url); - } - else if (scope.ask_variables_on_launch){ - scope.$emit('PromptForVars', html, url); - } - else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) { - scope.$emit('PromptForSurvey', html, url); - } - else { - scope.$emit('StartPlaybookRun', url); - } - } + if (scope.removeCredentialReady) { + scope.removeCredentialReady(); + } + scope.removeCredentialReady = scope.$on('CredentialReady', function(e, 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.sudo_password === "ASK"){ + passwords.push("sudo_password"); + } + if(data.su_password === "ASK"){ + passwords.push("su_password"); + } + if(data.vault_password === "ASK"){ + passwords.push("vault_password"); + } + if(passwords.length>0){ + scope.passwords_needed_to_start = passwords; + scope.$emit('PromptForPasswords', passwords, html, url); + } + else if (scope.ask_variables_on_launch){ + scope.$emit('PromptForVars', html, url); + } + else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) { + scope.$emit('PromptForSurvey', html, url); + } + else { + scope.$emit('StartPlaybookRun', url); + } + } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job template details. GET returned status: ' + status }); - }); - } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get job template details. GET returned status: ' + status }); + }); + } - }); + }); - // Get the job or job_template record - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - new_job_id = data.id; - launch_url = url;//data.related.start; - scope.passwords_needed_to_start = data.passwords_needed_to_start; - scope.prompt_for_vars = data.ask_variables_on_launch; - scope.survey_enabled = data.survey_enabled; - scope.ask_variables_on_launch = data.ask_variables_on_launch; - scope.variables_needed_to_start = data.variables_needed_to_start; - html = '
'; + // Get the job or job_template record + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + new_job_id = data.id; + launch_url = url;//data.related.start; + scope.passwords_needed_to_start = data.passwords_needed_to_start; + scope.prompt_for_vars = data.ask_variables_on_launch; + scope.survey_enabled = data.survey_enabled; + scope.ask_variables_on_launch = data.ask_variables_on_launch; + scope.variables_needed_to_start = data.variables_needed_to_start; + html = ''; - if(data.credential_needed_to_start === true){ - scope.$emit('PromptForCredential'); - } - else if (!Empty(data.passwords_needed_to_start) && data.passwords_needed_to_start.length > 0) { - scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url); - } - else if (data.ask_variables_on_launch) { - scope.$emit('PromptForVars', html, url); - } - else if (!Empty(data.survey_enabled) && data.survey_enabled===true) { - scope.$emit('PromptForSurvey', html, url); - } - else { - scope.$emit('StartPlaybookRun', url); - } + if(data.credential_needed_to_start === true){ + scope.$emit('PromptForCredential'); + } + else if (!Empty(data.passwords_needed_to_start) && data.passwords_needed_to_start.length > 0) { + scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url); + } + else if (data.ask_variables_on_launch) { + scope.$emit('PromptForVars', html, url); + } + else if (!Empty(data.survey_enabled) && data.survey_enabled===true) { + scope.$emit('PromptForSurvey', html, url); + } + else { + scope.$emit('StartPlaybookRun', url); + } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job template details. GET returned status: ' + status }); - }); - }; - } -]) + }) + .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', - 'ProjectsForm', 'Wait', - function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, ProjectsForm, Wait) { - return function (params) { - var scope = params.scope, - project_id = params.project_id, - url = GetBasePath('projects') + project_id + '/update/', - project; + // Submit SCM Update request + .factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', + 'ProjectsForm', 'Wait', + function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, ProjectsForm, 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'); - if (scope.refresh) { - scope.refresh(); - } - } - }); + 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'); + 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.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' }); - }); + 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 }); - }); - }; - } -]) + // 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', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait', - function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) { - return function (params) { + // Submit Inventory Update request + .factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait', + function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) { + return function (params) { - var scope = params.scope, - url = params.url, - inventory_source; + 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'); - if (scope.refreshGroups) { - // inventory detail page - scope.refreshGroups(); - } - else if (scope.refresh) { - scope.refresh(); - } - } - }); + 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'); + 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.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' }); - }); + 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 }); - }); - }; - } -]); + // 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/static/js/helpers/JobTemplates.js b/awx/ui/static/js/helpers/JobTemplates.js index 5577047ff7..47de4d535e 100644 --- a/awx/ui/static/js/helpers/JobTemplates.js +++ b/awx/ui/static/js/helpers/JobTemplates.js @@ -6,13 +6,13 @@ * Routines shared by job related controllers * */ - /** - * @ngdoc function - * @name helpers.function:JobTemplatesHelper - * @description Routines shared by job related controllers +/** +* @ngdoc function +* @name helpers.function:JobTemplatesHelper +* @description Routines shared by job related controllers */ - +export default angular.module('JobTemplatesHelper', ['Utilities']) /* @@ -21,181 +21,180 @@ angular.module('JobTemplatesHelper', ['Utilities']) */ .factory('CallbackHelpInit', ['$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm', '$routeParams', 'LoadBreadCrumbs', 'ProcessErrors', 'ParseTypeChange', - 'ParseVariableString', 'Empty', 'LookUpInit', 'InventoryList', 'CredentialList','ProjectList', 'RelatedSearchInit', 'RelatedPaginateInit', - function($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $routeParams, LoadBreadCrumbs, ProcessErrors,ParseTypeChange, - ParseVariableString, Empty, LookUpInit, InventoryList, CredentialList, ProjectList, RelatedSearchInit, RelatedPaginateInit) { - return function(params) { + 'ParseVariableString', 'Empty', 'LookUpInit', 'InventoryList', 'CredentialList','ProjectList', 'RelatedSearchInit', 'RelatedPaginateInit', + function($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $routeParams, LoadBreadCrumbs, ProcessErrors,ParseTypeChange, + ParseVariableString, Empty, LookUpInit, InventoryList, CredentialList, ProjectList, RelatedSearchInit, RelatedPaginateInit) { + 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 = $routeParams.template_id, - relatedSets = {}; - // checkSCMStatus, getPlaybooks, callback, - // choicesCount = 0; + var scope = params.scope, + defaultUrl = GetBasePath('job_templates'), + // generator = GenerateForm, + form = JobTemplateForm(), + // loadingFinishedCount = 0, + // base = $location.path().replace(/^\//, '').split('/')[0], + master = {}, + id = $routeParams.template_id, + relatedSets = {}; + // checkSCMStatus, getPlaybooks, callback, + // choicesCount = 0; - // 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 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(); - }); + // 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(); - }; + // 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(); + // 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(); - scope.fillJobTemplate = function(){ - // id = id || $rootScope.copy.id; - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl + id); - Rest.get() - .success(function (data) { - var fld, i; - LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name }); - for (fld in form.fields) { - if (fld !== 'variables' && 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(fld ==='survey_enabled'){ - // $scope.$emit('EnableSurvey', fld); - $('#job_templates_survey_enabled_chbox').attr('checked', scope[fld]); - if(Empty(data.summary_fields.survey)) { - $('#job_templates_delete_survey_btn').hide(); - $('#job_templates_edit_survey_btn').hide(); - $('#job_templates_create_survey_btn').show(); - } - else{ - $('#job_templates_delete_survey_btn').show(); - $('#job_templates_edit_survey_btn').show(); - $('#job_templates_create_survey_btn').hide(); - 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]; - } - } + scope.fillJobTemplate = function(){ + // id = id || $rootScope.copy.id; + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl + id); + Rest.get() + .success(function (data) { + var fld, i; + LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name }); + for (fld in form.fields) { + if (fld !== 'variables' && 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(fld ==='survey_enabled'){ + // $scope.$emit('EnableSurvey', fld); + $('#job_templates_survey_enabled_chbox').attr('checked', scope[fld]); + if(Empty(data.summary_fields.survey)) { + $('#job_templates_delete_survey_btn').hide(); + $('#job_templates_edit_survey_btn').hide(); + $('#job_templates_create_survey_btn').show(); + } + else{ + $('#job_templates_delete_survey_btn').show(); + $('#job_templates_edit_survey_btn').show(); + $('#job_templates_create_survey_btn').hide(); + 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]; + } + } - scope.url = data.url; + scope.url = data.url; - scope.ask_variables_on_launch = (data.ask_variables_on_launch) ? 'true' : 'false'; - master.ask_variables_on_launch = scope.ask_variables_on_launch; + scope.ask_variables_on_launch = (data.ask_variables_on_launch) ? 'true' : 'false'; + master.ask_variables_on_launch = scope.ask_variables_on_launch; - relatedSets = form.relatedSets(data.related); + relatedSets = form.relatedSets(data.related); - if (data.host_config_key) { - scope.example_config_key = data.host_config_key; - } - scope.example_template_id = id; - scope.setCallbackHelp(); + if (data.host_config_key) { + scope.example_config_key = data.host_config_key; + } + scope.example_template_id = id; + scope.setCallbackHelp(); - scope.callback_url = scope.callback_server_path + ((data.related.callback) ? data.related.callback : - GetBasePath('job_templates') + id + '/callback/'); - master.callback_url = scope.callback_url; + scope.callback_url = scope.callback_server_path + ((data.related.callback) ? data.related.callback : + GetBasePath('job_templates') + id + '/callback/'); + master.callback_url = scope.callback_url; - scope.can_edit = data.summary_fields.can_edit; + scope.can_edit = data.summary_fields.can_edit; - LookUpInit({ - scope: scope, - form: form, - current_item: data.inventory, - list: InventoryList, - field: 'inventory', - input_type: "radio" - }); + LookUpInit({ + scope: scope, + form: form, + current_item: data.inventory, + list: InventoryList, + field: 'inventory', + input_type: "radio" + }); - LookUpInit({ - url: GetBasePath('credentials') + '?kind=ssh', - scope: scope, - form: form, - current_item: data.credential, - list: CredentialList, - field: 'credential', - hdr: 'Select Machine Credential', - input_type: "radio" - }); + LookUpInit({ + url: GetBasePath('credentials') + '?kind=ssh', + scope: scope, + form: form, + current_item: data.credential, + list: CredentialList, + field: 'credential', + hdr: 'Select Machine Credential', + input_type: "radio" + }); - LookUpInit({ - scope: scope, - form: form, - current_item: data.project, - list: ProjectList, - field: 'project', - input_type: "radio" - }); + LookUpInit({ + scope: scope, + form: form, + current_item: data.project, + list: ProjectList, + field: 'project', + input_type: "radio" + }); - RelatedSearchInit({ - scope: scope, - form: form, - relatedSets: relatedSets - }); + RelatedSearchInit({ + scope: scope, + form: form, + relatedSets: relatedSets + }); - RelatedPaginateInit({ - scope: scope, - relatedSets: relatedSets - }); + RelatedPaginateInit({ + scope: scope, + relatedSets: relatedSets + }); - scope.$emit('jobTemplateLoaded', data.related.cloud_credential, master); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to retrieve job template: ' + $routeParams.template_id + '. GET status: ' + status - }); - }); - }; + scope.$emit('jobTemplateLoaded', data.related.cloud_credential, master); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to retrieve job template: ' + $routeParams.template_id + '. GET status: ' + status + }); + }); + }; - }; + }; -}]); - + }]); diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index 4a15962cd6..ef0cf3aad9 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -12,585 +12,585 @@ * @description routines shared by job related controllers */ +export default + angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers', + 'JobSubmissionHelper', 'LogViewerHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator']) -angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers', - 'JobSubmissionHelper', 'LogViewerHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator']) + /** + * JobsControllerInit({ scope: $scope }); + * + * Initialize calling scope with all the bits required to support a jobs list + * + */ + .factory('JobsControllerInit', ['$location', 'Find', 'DeleteJob', 'RelaunchJob', 'LogViewer', '$window', + function($location, Find, DeleteJob, RelaunchJob, LogViewer, $window) { + return function(params) { + var scope = params.scope, + iterator = (params.iterator) ? params.iterator : scope.iterator, + base = $location.path().replace(/^\//, '').split('/')[0]; -/** - * JobsControllerInit({ scope: $scope }); - * - * Initialize calling scope with all the bits required to support a jobs list - * - */ -.factory('JobsControllerInit', ['$location', 'Find', 'DeleteJob', 'RelaunchJob', 'LogViewer', '$window', - function($location, Find, DeleteJob, RelaunchJob, LogViewer, $window) { - return function(params) { - var scope = params.scope, - iterator = (params.iterator) ? params.iterator : scope.iterator, - base = $location.path().replace(/^\//, '').split('/')[0]; + scope.deleteJob = function(id) { + DeleteJob({ scope: scope, id: id }); + }; - scope.deleteJob = function(id) { - DeleteJob({ scope: scope, id: id }); - }; + scope.relaunchJob = function(event, id) { + var list, job, typeId; + try { + $(event.target).tooltip('hide'); + } + catch(e) { + //ignore + } + if (scope.completed_jobs) { + list = scope.completed_jobs; + } + else if (scope.running_jobs) { + list = scope.running_jobs; + } + else if (scope.queued_jobs) { + list = scope.queued_jobs; + } + else if (scope.jobs) { + list = scope.jobs; + } + job = Find({ list: list, key: 'id', val: id }); + if (job.type === 'inventory_update') { + typeId = job.inventory_source; + } + else if (job.type === 'project_update') { + typeId = job.project; + } + else if (job.type === 'job' || job.type === "system_job") { + typeId = job.id; + } + RelaunchJob({ scope: scope, id: typeId, type: job.type, name: job.name }); + }; - scope.relaunchJob = function(event, id) { - var list, job, typeId; - try { - $(event.target).tooltip('hide'); - } - catch(e) { - //ignore - } - if (scope.completed_jobs) { - list = scope.completed_jobs; - } - else if (scope.running_jobs) { - list = scope.running_jobs; - } - else if (scope.queued_jobs) { - list = scope.queued_jobs; - } - else if (scope.jobs) { - list = scope.jobs; - } - job = Find({ list: list, key: 'id', val: id }); - if (job.type === 'inventory_update') { - typeId = job.inventory_source; - } - else if (job.type === 'project_update') { - typeId = job.project; - } - else if (job.type === 'job' || job.type === "system_job") { - typeId = job.id; - } - RelaunchJob({ scope: scope, id: typeId, type: job.type, name: job.name }); - }; + scope.refreshJobs = function() { + if (base !== 'jobs') { + scope.search(iterator); + } - scope.refreshJobs = function() { - if (base !== 'jobs') { - scope.search(iterator); - } + }; - }; - - scope.viewJobLog = function(id) { - var list, job; - if (scope.completed_jobs) { - list = scope.completed_jobs; - } - else if (scope.running_jobs) { - list = scope.running_jobs; - } - else if (scope.queued_jobs) { - list = scope.queued_jobs; - } - else if (scope.jobs) { - list = scope.jobs; - } - else if(scope.portal_jobs){ - list=scope.portal_jobs; - } - job = Find({ list: list, key: 'id', val: id }); - if (job.type === 'job') { - if(scope.$parent.portalMode===true){ - $window.open('/#/jobs/' + job.id, '_blank'); + scope.viewJobLog = function(id) { + var list, job; + if (scope.completed_jobs) { + list = scope.completed_jobs; + } + else if (scope.running_jobs) { + list = scope.running_jobs; + } + else if (scope.queued_jobs) { + list = scope.queued_jobs; + } + else if (scope.jobs) { + list = scope.jobs; + } + else if(scope.portal_jobs){ + list=scope.portal_jobs; + } + job = Find({ list: list, key: 'id', val: id }); + if (job.type === 'job') { + if(scope.$parent.portalMode===true){ + $window.open('/#/jobs/' + job.id, '_blank'); + } + else { + $location.url('/jobs/' + job.id); + } } else { - $location.url('/jobs/' + job.id); + LogViewer({ + scope: scope, + url: job.url + }); } + }; + }; + } + ]) + + .factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', + function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM) { + 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 { - LogViewer({ - scope: scope, - url: job.url - }); + else if (type === 'job' || type === 'system_job') { + RelaunchPlaybook({ scope: scope, id: id, name: name }); + } + else if (type === 'project_update') { + RelaunchSCM({ scope: scope, id: id }); } }; - }; - } -]) + } + ]) -.factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', - function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM) { + .factory('JobStatusToolTip', [ + function () { + 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; + }; + } + ]) + + .factory('ShowJobSummary', ['Rest', 'Wait', 'GetBasePath', 'FormatDate', 'ProcessErrors', 'GenerateForm', 'JobSummary', + 'WatchInventoryWindowResize', + function (Rest, Wait, GetBasePath, FormatDate, ProcessErrors, GenerateForm, JobSummary, WatchInventoryWindowResize) { + return function (params) { + // Display status info in a modal dialog- called from inventory edit page + + var job_id = params.job_id, + generator = GenerateForm, + form = JobSummary, + scope, ww, wh, x, y, maxrows, url, html; + + html = '
' + + '
\n'; + + $('#inventory-modal-container').empty().append(html); + + scope = generator.inject(form, { mode: 'edit', id: 'form-container', breadCrumbs: false, related: false }); + + // Set modal dimensions based on viewport width + ww = $(document).width(); + wh = $('body').height(); + if (ww > 1199) { + // desktop + x = 675; + y = (750 > wh) ? wh - 20 : 750; + maxrows = 20; + } else if (ww <= 1199 && ww >= 768) { + x = 550; + y = (620 > wh) ? wh - 15 : 620; + maxrows = 15; + } else { + x = (ww - 20); + y = (500 > wh) ? wh : 500; + maxrows = 10; + } + + // Create the modal + $('#status-modal-dialog').dialog({ + buttons: { + 'OK': function () { + $(this).dialog('close'); + } + }, + modal: true, + width: x, + height: y, + autoOpen: false, + closeOnEscape: false, + create: function () { + // fix the close button + $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button') + .empty().attr({ + 'class': 'close' + }).text('x'); + // fix the OK button + $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-buttonset button:first') + .attr({ + 'class': 'btn btn-primary' + }); + }, + 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="status-modal-dialog"]'), + titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(), + buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(), + content = dialog.find('#status-modal-dialog'); + content.width(dialog.width() - 28); + content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) }); + }, + close: function () { + // Destroy on close + $('.tooltip').each(function () { + // Remove any lingering tooltip
elements + $(this).remove(); + }); + $('.popover').each(function () { + // remove lingering popover
elements + $(this).remove(); + }); + $('#status-modal-dialog').dialog('destroy'); + $('#inventory-modal-container').empty(); + WatchInventoryWindowResize(); + }, + open: function () { + Wait('stop'); + } + }); + + function calcRows(content) { + var n = content.match(/\n/g), + rows = (n) ? n.length : 1; + return (rows > maxrows) ? 20 : rows; + } + + Wait('start'); + url = GetBasePath('jobs') + job_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + var cDate; + scope.id = data.id; + scope.name = data.name; + scope.status = data.status; + scope.result_stdout = data.result_stdout; + scope.result_traceback = data.result_traceback; + scope.stdout_rows = calcRows(scope.result_stdout); + scope.traceback_rows = calcRows(scope.result_traceback); + cDate = new Date(data.created); + scope.created = FormatDate(cDate); + $('#status-modal-dialog').dialog('open'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Attempt to load job failed. GET returned status: ' + status }); + }); + }; + + } + ]) + + + .factory('JobsListUpdate', ['Rest', function(Rest) { 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 === 'job' || type === 'system_job') { - RelaunchPlaybook({ scope: scope, id: id, name: name }); - } - else if (type === 'project_update') { - RelaunchSCM({ scope: scope, id: id }); - } - }; - } -]) + parent_scope = params.parent_scope, + list = params.list; -.factory('JobStatusToolTip', [ - function () { - 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; - }; - } -]) + scope[list.name].forEach(function(item, item_idx) { + var fld, field, + itm = scope[list.name][item_idx]; -.factory('ShowJobSummary', ['Rest', 'Wait', 'GetBasePath', 'FormatDate', 'ProcessErrors', 'GenerateForm', 'JobSummary', - 'WatchInventoryWindowResize', - function (Rest, Wait, GetBasePath, FormatDate, ProcessErrors, GenerateForm, JobSummary, WatchInventoryWindowResize) { - return function (params) { - // Display status info in a modal dialog- called from inventory edit page + //if (item.type === 'inventory_update') { + // itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,''); + //} - var job_id = params.job_id, - generator = GenerateForm, - form = JobSummary, - scope, ww, wh, x, y, maxrows, url, html; - - html = '
' + - '
\n'; - - $('#inventory-modal-container').empty().append(html); - - scope = generator.inject(form, { mode: 'edit', id: 'form-container', breadCrumbs: false, related: false }); - - // Set modal dimensions based on viewport width - ww = $(document).width(); - wh = $('body').height(); - if (ww > 1199) { - // desktop - x = 675; - y = (750 > wh) ? wh - 20 : 750; - maxrows = 20; - } else if (ww <= 1199 && ww >= 768) { - x = 550; - y = (620 > wh) ? wh - 15 : 620; - maxrows = 15; - } else { - x = (ww - 20); - y = (500 > wh) ? wh : 500; - maxrows = 10; - } - - // Create the modal - $('#status-modal-dialog').dialog({ - buttons: { - 'OK': function () { - $(this).dialog('close'); - } - }, - modal: true, - width: x, - height: y, - autoOpen: false, - closeOnEscape: false, - create: function () { - // fix the close button - $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button') - .empty().attr({ - 'class': 'close' - }).text('x'); - // fix the OK button - $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-buttonset button:first') - .attr({ - 'class': 'btn btn-primary' - }); - }, - 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="status-modal-dialog"]'), - titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(), - buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(), - content = dialog.find('#status-modal-dialog'); - content.width(dialog.width() - 28); - content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) }); - }, - close: function () { - // Destroy on close - $('.tooltip').each(function () { - // Remove any lingering tooltip
elements - $(this).remove(); + // Set the item type label + if (list.fields.type) { + parent_scope.type_choices.every(function(choice) { + if (choice.value === item.type) { + itm.type_label = choice.label; + return false; + } + return true; }); - $('.popover').each(function () { - // remove lingering popover
elements - $(this).remove(); - }); - $('#status-modal-dialog').dialog('destroy'); - $('#inventory-modal-container').empty(); - WatchInventoryWindowResize(); - }, - open: function () { - Wait('stop'); } - }); - - function calcRows(content) { - var n = content.match(/\n/g), - rows = (n) ? n.length : 1; - return (rows > maxrows) ? 20 : rows; - } - - Wait('start'); - url = GetBasePath('jobs') + job_id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - var cDate; - scope.id = data.id; - scope.name = data.name; - scope.status = data.status; - scope.result_stdout = data.result_stdout; - scope.result_traceback = data.result_traceback; - scope.stdout_rows = calcRows(scope.result_stdout); - scope.traceback_rows = calcRows(scope.result_traceback); - cDate = new Date(data.created); - scope.created = FormatDate(cDate); - $('#status-modal-dialog').dialog('open'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Attempt to load job failed. GET returned status: ' + status }); - }); - }; - - } -]) - - -.factory('JobsListUpdate', ['Rest', function(Rest) { - 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.every(function(choice) { - if (choice.value === item.type) { - itm.type_label = choice.label; + // Set the job status label + parent_scope.status_choices.every(function(status) { + if (status.value === item.status) { + itm.status_label = status.label; return false; } return true; }); - } - // Set the job status label - parent_scope.status_choices.every(function(status) { - if (status.value === item.status) { - itm.status_label = status.label; - return false; + //Set the name link + if (item.type === "inventory_update") { + Rest.setUrl(item.related.inventory_source); + Rest.get() + .success(function(data) { + itm.nameHref = "/home/groups?id=" + data.group; + }); + } + else if (item.type === "project_update") { + itm.nameHref = "/projects/" + item.project; + } + else if (item.type === "job") { + itm.nameHref = ""; + } + + 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]; + } + } } - return true; }); - //Set the name link - if (item.type === "inventory_update") { - Rest.setUrl(item.related.inventory_source); + }; + }]) + + /** + * + * Called from JobsList controller to load each section or list on the page + * + */ + .factory('LoadJobsScope', ['$routeParams', '$location', '$compile', 'SearchInit', 'PaginateInit', 'GenerateList', 'JobsControllerInit', 'JobsListUpdate', 'SearchWidget', + function($routeParams, $location, $compile, SearchInit, PaginateInit, GenerateList, JobsControllerInit, JobsListUpdate, SearchWidget) { + return function(params) { + var parent_scope = params.parent_scope, + scope = params.scope, + list = params.list, + id = params.id, + url = params.url, + pageSize = params.pageSize || 5, + base = $location.path().replace(/^\//, '').split('/')[0], + search_params = params.searchParams, + spinner = (params.spinner === undefined) ? true : params.spinner, + e, html, key; + + // Add the search widget. We want it arranged differently, so we're injecting and compiling it separately + html = SearchWidget({ + iterator: list.iterator, + template: params.list, + includeSize: false + }); + e = angular.element(document.getElementById(id + '-search-container')).append(html); + $compile(e)(scope); + + GenerateList.inject(list, { + mode: 'edit', + id: id, + breadCrumbs: false, + scope: scope, + showSearch: false + }); + + SearchInit({ + scope: scope, + set: list.name, + list: list, + url: url + }); + + PaginateInit({ + scope: scope, + list: list, + url: url, + pageSize: pageSize + }); + + scope.iterator = list.iterator; + + if (scope.removePostRefresh) { + scope.removePostRefresh(); + } + scope.$on('PostRefresh', function(){ + JobsControllerInit({ scope: scope, parent_scope: parent_scope }); + JobsListUpdate({ scope: scope, parent_scope: parent_scope, list: list }); + parent_scope.$emit('listLoaded'); + // setTimeout(function(){ + // scope.$apply(); + // }, 300); + }); + + if (base === 'jobs' && list.name === 'completed_jobs') { + if ($routeParams.id__int) { + scope[list.iterator + 'SearchField'] = 'id'; + scope[list.iterator + 'SearchValue'] = $routeParams.id__int; + scope[list.iterator + 'SearchFieldLabel'] = 'Job ID'; + } + } + + if (search_params) { + for (key in search_params) { + scope[key] = search_params[key]; + } + } + scope.search(list.iterator, null, null, null, null, spinner); + }; + }]) + + .factory('DeleteJob', ['Find', 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Prompt', 'Alert', + function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert){ + 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.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 = 'Cancel Job'; + } else { + url = job.url; + action_label = 'delete'; + hdr = 'Delete Job'; + } + + 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 { + scope.search(scope.iterator); + } + }) + .error(function() { + Wait('stop'); + $('#prompt-modal').modal('hide'); + // 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 { + scope.search(scope.iterator); + } + }) + .error(function () { + Wait('stop'); + $('#prompt-modal').modal('hide'); + // 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 body; + body = (action_label === 'cancel' || job.status === 'new') ? "Submit the request to cancel" : "Delete"; + Prompt({ + hdr: hdr, + body: "
" + body + " job #" + id + " " + job.name + "?
", + action: action + }); + }); + + if (action_label === 'cancel') { + Rest.setUrl(url); Rest.get() .success(function(data) { - itm.nameHref = "/home/groups?id=" + data.group; + 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 if (item.type === "project_update") { - itm.nameHref = "/projects/" + item.project; - } - else if (item.type === "job") { - itm.nameHref = ""; + else { + scope.$emit('CancelJob'); } - 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]; - } - } - } - }); - }; -}]) - -/** - * - * Called from JobsList controller to load each section or list on the page - * - */ -.factory('LoadJobsScope', ['$routeParams', '$location', '$compile', 'SearchInit', 'PaginateInit', 'GenerateList', 'JobsControllerInit', 'JobsListUpdate', 'SearchWidget', - function($routeParams, $location, $compile, SearchInit, PaginateInit, GenerateList, JobsControllerInit, JobsListUpdate, SearchWidget) { - return function(params) { - var parent_scope = params.parent_scope, - scope = params.scope, - list = params.list, - id = params.id, - url = params.url, - pageSize = params.pageSize || 5, - base = $location.path().replace(/^\//, '').split('/')[0], - search_params = params.searchParams, - spinner = (params.spinner === undefined) ? true : params.spinner, - e, html, key; - - // Add the search widget. We want it arranged differently, so we're injecting and compiling it separately - html = SearchWidget({ - iterator: list.iterator, - template: params.list, - includeSize: false - }); - e = angular.element(document.getElementById(id + '-search-container')).append(html); - $compile(e)(scope); - - GenerateList.inject(list, { - mode: 'edit', - id: id, - breadCrumbs: false, - scope: scope, - showSearch: false - }); - - SearchInit({ - scope: scope, - set: list.name, - list: list, - url: url - }); - - PaginateInit({ - scope: scope, - list: list, - url: url, - pageSize: pageSize - }); - - scope.iterator = list.iterator; - - if (scope.removePostRefresh) { - scope.removePostRefresh(); - } - scope.$on('PostRefresh', function(){ - JobsControllerInit({ scope: scope, parent_scope: parent_scope }); - JobsListUpdate({ scope: scope, parent_scope: parent_scope, list: list }); - parent_scope.$emit('listLoaded'); - // setTimeout(function(){ - // scope.$apply(); - // }, 300); - }); - - if (base === 'jobs' && list.name === 'completed_jobs') { - if ($routeParams.id__int) { - scope[list.iterator + 'SearchField'] = 'id'; - scope[list.iterator + 'SearchValue'] = $routeParams.id__int; - scope[list.iterator + 'SearchFieldLabel'] = 'Job ID'; - } - } - - if (search_params) { - for (key in search_params) { - scope[key] = search_params[key]; - } - } - scope.search(list.iterator, null, null, null, null, spinner); - }; -}]) - -.factory('DeleteJob', ['Find', 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Prompt', 'Alert', -function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert){ - 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.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 = 'Cancel Job'; - } else { - url = job.url; - action_label = 'delete'; - hdr = 'Delete Job'; - } - - action = function () { + .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); - if (action_label === 'cancel') { - Rest.post() - .success(function () { - $('#prompt-modal').modal('hide'); - if (callback) { - scope.$emit(callback, action_label); - } - else { - scope.search(scope.iterator); - } - }) - .error(function() { - Wait('stop'); - $('#prompt-modal').modal('hide'); - // 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 { - scope.search(scope.iterator); - } - }) - .error(function () { - Wait('stop'); - $('#prompt-modal').modal('hide'); - // 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 body; - body = (action_label === 'cancel' || job.status === 'new') ? "Submit the request to cancel" : "Delete"; - Prompt({ - hdr: hdr, - body: "
" + body + " job #" + id + " " + job.name + "?
", - action: action - }); - }); - - if (action_label === 'cancel') { Rest.setUrl(url); Rest.get() - .success(function(data) { - if (data.can_cancel) { - scope.$emit('CancelJob'); - } - else { - scope.$emit('CancelNotAllowed'); - } + .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: 'Call to ' + url + - ' failed. GET returned: ' + status }); + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + + url + ' GET returned: ' + status }); }); - } - else { - scope.$emit('CancelJob'); - } + }; + }]) - }; -}]) + .factory('RelaunchPlaybook', ['PlaybookRun', function(PlaybookRun) { + return function(params) { + var scope = params.scope, + id = params.id; + PlaybookRun({ scope: scope, id: id }); + }; + }]) -.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', ['PlaybookRun', function(PlaybookRun) { - return function(params) { - var scope = params.scope, - id = params.id; - PlaybookRun({ scope: scope, id: id }); - }; -}]) - -.factory('RelaunchSCM', ['ProjectUpdate', function(ProjectUpdate) { - return function(params) { - var scope = params.scope, - id = params.id; - ProjectUpdate({ scope: scope, project_id: id }); - }; -}]); + .factory('RelaunchSCM', ['ProjectUpdate', function(ProjectUpdate) { + return function(params) { + var scope = params.scope, + id = params.id; + ProjectUpdate({ scope: scope, project_id: id }); + }; + }]); diff --git a/awx/ui/static/js/helpers/License.js b/awx/ui/static/js/helpers/License.js index 0ea8efa552..ad0c36e127 100644 --- a/awx/ui/static/js/helpers/License.js +++ b/awx/ui/static/js/helpers/License.js @@ -18,228 +18,378 @@ * */ +import 'tower/forms'; -angular.module('LicenseHelper', ['RestServices', 'Utilities', 'LicenseUpdateFormDefinition', 'FormGenerator', 'ParseHelper', 'ModalDialog', 'VariablesHelper', 'LicenseFormDefinition', 'AccessHelper']) +export default + angular.module('LicenseHelper', ['RestServices', 'Utilities', 'LicenseUpdateFormDefinition', 'FormGenerator', 'ParseHelper', 'ModalDialog', 'VariablesHelper', 'LicenseFormDefinition', 'AccessHelper']) -.factory('CheckLicense', ['$rootScope', '$compile', 'CreateDialog', 'Store', 'LicenseUpdateForm', 'GenerateForm', 'TextareaResize', 'ToJSON', 'GetBasePath', 'Rest', 'ProcessErrors', 'Alert', 'IsAdmin', -function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateForm, TextareaResize, ToJSON, GetBasePath, Rest, ProcessErrors, Alert, IsAdmin) { - return { - getRemainingDays: function(time_remaining) { - // assumes time_remaining will be in seconds - var tr = parseInt(time_remaining, 10); - return Math.floor(tr / 86400); - }, + .factory('CheckLicense', ['$rootScope', '$compile', 'CreateDialog', 'Store', 'LicenseUpdateForm', 'GenerateForm', 'TextareaResize', 'ToJSON', 'GetBasePath', 'Rest', 'ProcessErrors', 'Alert', 'IsAdmin', + function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateForm, TextareaResize, ToJSON, GetBasePath, Rest, ProcessErrors, Alert, IsAdmin) { + return { + getRemainingDays: function(time_remaining) { + // assumes time_remaining will be in seconds + var tr = parseInt(time_remaining, 10); + return Math.floor(tr / 86400); + }, - shouldNotify: function(license) { - if (license && typeof license === 'object' && Object.keys(license).length > 0) { - // we have a license object - if (!license.valid_key) { - // missing valid key + shouldNotify: function(license) { + if (license && typeof license === 'object' && Object.keys(license).length > 0) { + // we have a license object + if (!license.valid_key) { + // missing valid key + return true; + } + else if (license.free_instances <= 0) { + // host count exceeded + return true; + } + else if (this.getRemainingDays(license.time_remaining) < 15) { + // below 15 days remaining on license + return true; + } + return false; + } else { + // missing license object return true; } - else if (license.free_instances <= 0) { - // host count exceeded - return true; - } - else if (this.getRemainingDays(license.time_remaining) < 15) { - // below 15 days remaining on license - return true; - } - return false; - } else { - // missing license object - return true; - } - }, + }, - isAdmin: function() { - return IsAdmin(); - }, + isAdmin: function() { + return IsAdmin(); + }, - getHTML: function(license, includeFormButton) { + getHTML: function(license, includeFormButton) { - var title, html, - contact_us = "contact us ", - renew = "ansible.com/renew ", - pricing = "ansible.com/pricing ", - license_link = "click here", - result = {}, - license_is_valid=false; + var title, html, + contact_us = "contact us ", + renew = "ansible.com/renew ", + pricing = "ansible.com/pricing ", + license_link = "click here", + result = {}, + license_is_valid=false; - if (license && typeof license === 'object' && Object.keys(license).length > 0 && license.valid_key !== undefined) { - // we have a license - if (!license.valid_key) { - title = "Invalid License"; - html = "

The Ansible Tower license is invalid.

"; - } - else if (this.getRemainingDays(license.time_remaining) <= 0) { - title = "License Expired"; - html = "
\n" + - "

Thank you for using Ansible Tower. The Ansible Tower license has expired

"; - if (parseInt(license.grace_period_remaining,10) > 86400) { + if (license && typeof license === 'object' && Object.keys(license).length > 0 && license.valid_key !== undefined) { + // we have a license + if (!license.valid_key) { + title = "Invalid License"; + html = "

The Ansible Tower license is invalid.

"; + } + else if (this.getRemainingDays(license.time_remaining) <= 0) { + title = "License Expired"; + html = "
\n" + + "

Thank you for using Ansible Tower. The Ansible Tower license has expired

"; + if (parseInt(license.grace_period_remaining,10) > 86400) { + // trial licenses don't get a grace period + if (license.trial) { + html += "

Don't worry — your existing history and content has not been affected, but playbooks will no longer run and new hosts cannot be added. " + + "If you are ready to upgrade, " + contact_us + " or visit " + pricing + " to see all of your license options. Thanks!

"; + } else { + html += "

Don't worry — your existing history and content has not been affected, but in " + this.getRemainingDays(license.grace_period_remaining) + " days playbooks will no longer " + + "run and new hosts cannot be added. If you are ready to upgrade, " + contact_us + " " + + "or visit ansible.com/pricing to see all of your license options. Thanks!

"; + } + } else { + html += "

Don’t worry — your existing history and content has not been affected, but playbooks will no longer run and new hosts cannot be added. If you are ready to renew or upgrade, contact us " + + "at " + renew + ". Thanks!

"; + } + } + else if (this.getRemainingDays(license.time_remaining) < 15) { + // Warning: license expiring in less than 15 days + title = "License Warning"; + html = "

Thank you for using Ansible Tower. The Ansible Tower license " + + "has " + this.getRemainingDays(license.time_remaining) + " days remaining.

"; // trial licenses don't get a grace period if (license.trial) { - html += "

Don't worry — your existing history and content has not been affected, but playbooks will no longer run and new hosts cannot be added. " + - "If you are ready to upgrade, " + contact_us + " or visit " + pricing + " to see all of your license options. Thanks!

"; + html += "

After this license expires, playbooks will no longer run and hosts cannot be added. If you are ready to upgrade, " + contact_us + " or visit " + pricing + " to see all of your license options. Thanks!

"; } else { - html += "

Don't worry — your existing history and content has not been affected, but in " + this.getRemainingDays(license.grace_period_remaining) + " days playbooks will no longer " + - "run and new hosts cannot be added. If you are ready to upgrade, " + contact_us + " " + - "or visit ansible.com/pricing to see all of your license options. Thanks!

"; + html += "

After this license expires, playbooks will no longer run and hosts cannot be added. If you are ready to renew or upgrade, contact us at " + renew + ". Thanks!

"; } - } else { - html += "

Don’t worry — your existing history and content has not been affected, but playbooks will no longer run and new hosts cannot be added. If you are ready to renew or upgrade, contact us " + - "at " + renew + ". Thanks!

"; - } - } - else if (this.getRemainingDays(license.time_remaining) < 15) { - // Warning: license expiring in less than 15 days - title = "License Warning"; - html = "

Thank you for using Ansible Tower. The Ansible Tower license " + - "has " + this.getRemainingDays(license.time_remaining) + " days remaining.

"; - // trial licenses don't get a grace period - if (license.trial) { - html += "

After this license expires, playbooks will no longer run and hosts cannot be added. If you are ready to upgrade, " + contact_us + " or visit " + pricing + " to see all of your license options. Thanks!

"; - } else { - html += "

After this license expires, playbooks will no longer run and hosts cannot be added. If you are ready to renew or upgrade, contact us at " + renew + ". Thanks!

"; - } - // If there is exactly one day remaining, change "days remaining" - // to "day remaining". - html = html.replace('has 1 days remaining', 'has 1 day remaining'); - } - else if (license.free_instances <= 0) { - title = "Host Count Exceeded"; - html = "

The Ansible Tower license has reached capacity for the number of managed hosts allowed. No new hosts can be added. Existing " + - "playbooks can still be run against hosts already in inventory.

" + - "

If you are ready to upgrade, contact us at " + renew + ". Thanks!

"; + // If there is exactly one day remaining, change "days remaining" + // to "day remaining". + html = html.replace('has 1 days remaining', 'has 1 day remaining'); + } + else if (license.free_instances <= 0) { + title = "Host Count Exceeded"; + html = "

The Ansible Tower license has reached capacity for the number of managed hosts allowed. No new hosts can be added. Existing " + + "playbooks can still be run against hosts already in inventory.

" + + "

If you are ready to upgrade, contact us at " + renew + ". Thanks!

"; + } else { + // license is valid. the following text is displayed in the license viewer + title = "Update License"; + html = "

The Ansible Tower license is valid.

" + + "

If you are ready to upgrade, contact us at " + renew + ". Thanks!

"; + license_is_valid = true; + } } else { - // license is valid. the following text is displayed in the license viewer - title = "Update License"; - html = "

The Ansible Tower license is valid.

" + - "

If you are ready to upgrade, contact us at " + renew + ". Thanks!

"; - license_is_valid = true; + // No license + title = "Add Your License"; + html = "

Now that you’ve successfully installed or upgraded Ansible Tower, the next step is to add a license file. " + + "If you don’t have a license file yet, " + license_link + " to see all of our free and paid license options.

" + + "

Get a Free Tower Trial License

"; } - } else { - // No license - title = "Add Your License"; - html = "

Now that you’ve successfully installed or upgraded Ansible Tower, the next step is to add a license file. " + - "If you don’t have a license file yet, " + license_link + " to see all of our free and paid license options.

" + - "

Get a Free Tower Trial License

"; - } - if (IsAdmin()) { - html += "

Copy and paste the contents of the new license file in the field below and click the Submit button.

"; - } else { - html += "

A system administrator can install the new license by choosing View License on the Account Menu and clicking on the Update License tab.

"; - } + if (IsAdmin()) { + html += "

Copy and paste the contents of the new license file in the field below and click the Submit button.

"; + } else { + html += "

A system administrator can install the new license by choosing View License on the Account Menu and clicking on the Update License tab.

"; + } - html += "
"; + html += "
"; - if (IsAdmin()) { - html += GenerateForm.buildHTML(LicenseUpdateForm, { mode: 'edit', showButtons:((includeFormButton) ? true : false), breadCrumbs: false }); - } + if (IsAdmin()) { + html += GenerateForm.buildHTML(LicenseUpdateForm, { mode: 'edit', showButtons:((includeFormButton) ? true : false), breadCrumbs: false }); + } - html += "
"; + html += "
"; - result.body = html; - result.title = title; - return result; - }, + result.body = html; + result.title = title; + return result; + }, - postLicense: function(license_key, in_scope) { - var url = GetBasePath('config'), - self = this, - json_data, scope; + postLicense: function(license_key, in_scope) { + var url = GetBasePath('config'), + self = this, + json_data, scope; - scope = (in_scope) ? in_scope : self.scope; + scope = (in_scope) ? in_scope : self.scope; - json_data = ToJSON('json', license_key); - json_data.eula_accepted = scope.eula_agreement; - if (typeof json_data === 'object' && Object.keys(json_data).length > 0) { - Rest.setUrl(url); - Rest.post(json_data) - .success(function () { - try { + json_data = ToJSON('json', license_key); + json_data.eula_accepted = scope.eula_agreement; + if (typeof json_data === 'object' && Object.keys(json_data).length > 0) { + Rest.setUrl(url); + Rest.post(json_data) + .success(function () { + try { + $('#license-modal-dialog').dialog('close'); + } + catch(e) { + // ignore + } + Alert('License Accepted', 'The Ansible Tower license was updated. To view or update license information in the future choose View License from the Account menu.','alert-info'); + }) + .error(function (data, status) { + scope.license_json_api_error = "A valid license key in JSON format is required"; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to update license. POST returned: ' + status + }); + }); + } else { + scope.license_json_api_error = "A valid license key in JSON format is required"; + } + }, + + test: function() { + var license = Store('license'), + notify = this.shouldNotify(license), + self = this, + scope, height, html, buttons; + + self.scope = $rootScope.$new(); + scope = self.scope; + + if (license && typeof license === 'object' && Object.keys(license).length > 0) { + if (license.tested) { + return true; + } + license.tested = true; + Store('license',license); //update with tested flag + } + + // Don't do anything when the license is valid + if (!notify) { + return true; // if the license is valid it would exit 'test' here, otherwise it moves on to making the modal for the license + } + + html = this.getHTML(license); + $('#license-modal-dialog').html(html.body); + + scope.flashMessage = null; + scope.parseType = 'json'; + + scope.removeLicenseDialogReady = scope.$on('LicenseDialogReady', function() { + $('#license_license_json').attr('ng-required' , 'true' ); + $('#license_eula_agreement_chbox').attr('ng-required' , 'true' ); + $('#license-submit-button').attr('ng-disabled' , "license_form.$invalid" ); + var e = angular.element(document.getElementById('license-modal-dialog')); + $compile(e)(scope); + e = angular.element(document.getElementById('license-submit-button')); + $compile(e)(scope); + $('#license-modal-dialog').dialog('open'); + }); + + scope.submitLicenseKey = function() { + self.postLicense(scope.license_json); + }; + + if (IsAdmin()) { + buttons = [{ + label: "Cancel", + onClick: function() { $('#license-modal-dialog').dialog('close'); + }, + "class": "btn btn-default", + "id": "license-cancel-button" + }, { + label: "Submit", + onClick: function() { + scope.submitLicenseKey(); + }, + "class": "btn btn-primary", + "id": "license-submit-button" + }]; + } else { + buttons = [{ + label: "OK", + onClick: function() { + $('#license-modal-dialog').dialog('close'); + }, + "class": "btn btn-primary", + "id": "license-ok-button" + }]; + } + + height = (IsAdmin()) ? 675 : 350; + + if (scope.removeLicenseReady) { + scope.removeLicenseReady(); + } + scope.removeLicenseReady = scope.$on('LicenseReady', function(e, data) { + + scope.license_json = ""; + scope.eula = data.eula; + if (data.license_info && data.license_info.valid_key !== undefined) { + scope.license_json = JSON.stringify(data.license_info, null, ' '); + } + + CreateDialog({ + scope: scope, + buttons: buttons, + width: 675, + height: height, + minWidth: 400, + title: html.title, + id: 'license-modal-dialog', + clonseOnEscape: false, + onClose: function() { + if (scope.codeMirror) { + scope.codeMirror.destroy(); + } + $('#license-modal-dialog').empty(); + }, + onResizeStop: function() { + if (IsAdmin()) { + TextareaResize({ + scope: scope, + textareaId: 'license_license_json', + modalId: 'license-modal-dialog', + formId: 'license-notification-body', + fld: 'license_json', + parse: true, + onChange: function() { scope.license_json_api_error = ''; } + }); + } + }, + onOpen: function() { + if (IsAdmin()) { + setTimeout(function() { + TextareaResize({ + scope: scope, + textareaId: 'license_license_json', + modalId: 'license-modal-dialog', + formId: 'license-notification-body', + fld: 'license_json', + parse: true, + onChange: function() { scope.license_json_api_error = ''; } + }); + $('#cm-license_json-container .CodeMirror textarea').focus(); + }, 300); + } else { + $('#license-ok-button').focus(); + } + }, + callback: 'LicenseDialogReady' + }); + }); + + self.GetLicense('LicenseReady'); + + }, + + GetLicense: function(callback, inScope) { + // Retrieve license detail + var self = this, + scope = (inScope) ? inScope : self.scope, + url = GetBasePath('config'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + if (scope && callback) { + scope.$emit(callback, data); } - catch(e) { - // ignore + else if (scope) { + scope.$emit('CheckLicenseReady', data); } - Alert('License Accepted', 'The Ansible Tower license was updated. To view or update license information in the future choose View License from the Account menu.','alert-info'); }) .error(function (data, status) { - scope.license_json_api_error = "A valid license key in JSON format is required"; - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to update license. POST returned: ' + status + ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve license. GET status: ' + status }); }); - } else { - scope.license_json_api_error = "A valid license key in JSON format is required"; } - }, + }; + }]) - test: function() { - var license = Store('license'), - notify = this.shouldNotify(license), - self = this, - scope, height, html, buttons; + .factory('LicenseViewer', ['$location', '$rootScope', '$compile', '$filter', 'GenerateForm', 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', 'FormatDate', 'Prompt', 'Empty', 'LicenseForm', 'IsAdmin', 'CreateDialog', 'CheckLicense', 'TextareaResize', + function ($location, $rootScope, $compile, $filter, GenerateForm, Rest, Alert, GetBasePath, ProcessErrors, FormatDate, Prompt, Empty, LicenseForm, IsAdmin, CreateDialog, CheckLicense, TextareaResize) { + return { - self.scope = $rootScope.$new(); - scope = self.scope; + createDialog: function(html) { + var self = this, + scope = this.getScope(), + buttons; - if (license && typeof license === 'object' && Object.keys(license).length > 0) { - if (license.tested) { - return true; + if (scope.removeLicenseDialogReady) { + scope.removeLicenseDialogReady(); } - license.tested = true; - Store('license',license); //update with tested flag - } + scope.removeLicenseDialogReady = scope.$on('LicenseDialogReady', function() { + var e, h; - // Don't do anything when the license is valid - if (!notify) { - return true; // if the license is valid it would exit 'test' here, otherwise it moves on to making the modal for the license - } + e = angular.element(document.getElementById('license-modal-dialog')); + e.empty().html(html); - html = this.getHTML(license); - $('#license-modal-dialog').html(html.body); + if (scope.license_status === 'Invalid License Key' || scope.license_status === 'Missing License Key') { + $('#license_tabs li:eq(1)').hide(); + } - scope.flashMessage = null; - scope.parseType = 'json'; + scope.parseType = 'json'; + scope.license_json = JSON.stringify(self.license, null, ' '); + scope.eula = self.eula; + scope.eula_agreement = false; - scope.removeLicenseDialogReady = scope.$on('LicenseDialogReady', function() { - $('#license_license_json').attr('ng-required' , 'true' ); - $('#license_eula_agreement_chbox').attr('ng-required' , 'true' ); - $('#license-submit-button').attr('ng-disabled' , "license_form.$invalid" ); - var e = angular.element(document.getElementById('license-modal-dialog')); - $compile(e)(scope); - e = angular.element(document.getElementById('license-submit-button')); - $compile(e)(scope); - $('#license-modal-dialog').dialog('open'); - }); - scope.submitLicenseKey = function() { - self.postLicense(scope.license_json); - }; + h = CheckLicense.getHTML(self.getLicense(),true).body; + $('#license-modal-dialog #license_tabs').append("
  • Update License
  • "); + $('#license-modal-dialog .tab-content').append("
    "); + $('#license-modal-dialog #update_license').html(h); + + setTimeout(function() { + $('#license_license_json').attr('ng-required' , 'true' ); + $('#license_eula_agreement_chbox').attr('ng-required' , 'true' ); + $('#license_form_submit_btn').attr('ng-disabled' , "license_form.$invalid" ); + var e = angular.element(document.getElementById('license-modal-dialog')); + $compile(e)(scope); + $('#license-modal-dialog').dialog('open'); + }, 300); + }); + + scope.submitLicenseKey = function() { + CheckLicense.postLicense(scope.license_json, scope); + }; - if (IsAdmin()) { - buttons = [{ - label: "Cancel", - onClick: function() { - $('#license-modal-dialog').dialog('close'); - }, - "class": "btn btn-default", - "id": "license-cancel-button" - }, { - label: "Submit", - onClick: function() { - scope.submitLicenseKey(); - }, - "class": "btn btn-primary", - "id": "license-submit-button" - }]; - } else { buttons = [{ label: "OK", onClick: function() { @@ -248,28 +398,14 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF "class": "btn btn-primary", "id": "license-ok-button" }]; - } - - height = (IsAdmin()) ? 675 : 350; - - if (scope.removeLicenseReady) { - scope.removeLicenseReady(); - } - scope.removeLicenseReady = scope.$on('LicenseReady', function(e, data) { - - scope.license_json = ""; - scope.eula = data.eula; - if (data.license_info && data.license_info.valid_key !== undefined) { - scope.license_json = JSON.stringify(data.license_info, null, ' '); - } CreateDialog({ scope: scope, buttons: buttons, width: 675, - height: height, + height: (IsAdmin()) ? 745 : 600, minWidth: 400, - title: html.title, + title: 'Ansible Tower License', id: 'license-modal-dialog', clonseOnEscape: false, onClose: function() { @@ -286,6 +422,7 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF modalId: 'license-modal-dialog', formId: 'license-notification-body', fld: 'license_json', + bottom_margin: 90, parse: true, onChange: function() { scope.license_json_api_error = ''; } }); @@ -301,336 +438,200 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF formId: 'license-notification-body', fld: 'license_json', parse: true, + bottom_margin: 90, onChange: function() { scope.license_json_api_error = ''; } }); - $('#cm-license_json-container .CodeMirror textarea').focus(); }, 300); - } else { - $('#license-ok-button').focus(); } + $('#license-ok-button').focus(); + $('#update_license_link').on('click', function() { + if (IsAdmin()) { + TextareaResize({ + scope: scope, + textareaId: 'license_license_json', + modalId: 'license-modal-dialog', + formId: 'license-notification-body', + fld: 'license_json', + bottom_margin: 90, + parse: true, + onChange: function() { scope.license_json_api_error = ''; } + }); + } + }); }, callback: 'LicenseDialogReady' }); - }); + }, - self.GetLicense('LicenseReady'); + getDefaultHTML: function(license_info) { + var fld, html, + self = this, + generator = GenerateForm; - }, + self.form = angular.copy(LicenseForm); - GetLicense: function(callback, inScope) { - // Retrieve license detail - var self = this, - scope = (inScope) ? inScope : self.scope, - url = GetBasePath('config'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - if (scope && callback) { - scope.$emit(callback, data); - } - else if (scope) { - scope.$emit('CheckLicenseReady', data); - } - }) - .error(function (data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve license. GET status: ' + status - }); - }); - } - }; -}]) - -.factory('LicenseViewer', ['$location', '$rootScope', '$compile', '$filter', 'GenerateForm', 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', 'FormatDate', 'Prompt', 'Empty', 'LicenseForm', 'IsAdmin', 'CreateDialog', 'CheckLicense', 'TextareaResize', -function ($location, $rootScope, $compile, $filter, GenerateForm, Rest, Alert, GetBasePath, ProcessErrors, FormatDate, Prompt, Empty, LicenseForm, IsAdmin, CreateDialog, CheckLicense, TextareaResize) { - return { - - createDialog: function(html) { - var self = this, - scope = this.getScope(), - buttons; - - if (scope.removeLicenseDialogReady) { - scope.removeLicenseDialogReady(); - } - scope.removeLicenseDialogReady = scope.$on('LicenseDialogReady', function() { - var e, h; - - e = angular.element(document.getElementById('license-modal-dialog')); - e.empty().html(html); - - if (scope.license_status === 'Invalid License Key' || scope.license_status === 'Missing License Key') { - $('#license_tabs li:eq(1)').hide(); - } - - scope.parseType = 'json'; - scope.license_json = JSON.stringify(self.license, null, ' '); - scope.eula = self.eula; - scope.eula_agreement = false; - - - h = CheckLicense.getHTML(self.getLicense(),true).body; - $('#license-modal-dialog #license_tabs').append("
  • Update License
  • "); - $('#license-modal-dialog .tab-content').append("
    "); - $('#license-modal-dialog #update_license').html(h); - - setTimeout(function() { - $('#license_license_json').attr('ng-required' , 'true' ); - $('#license_eula_agreement_chbox').attr('ng-required' , 'true' ); - $('#license_form_submit_btn').attr('ng-disabled' , "license_form.$invalid" ); - var e = angular.element(document.getElementById('license-modal-dialog')); - $compile(e)(scope); - $('#license-modal-dialog').dialog('open'); - }, 300); - }); - - scope.submitLicenseKey = function() { - CheckLicense.postLicense(scope.license_json, scope); - }; - - buttons = [{ - label: "OK", - onClick: function() { - $('#license-modal-dialog').dialog('close'); - }, - "class": "btn btn-primary", - "id": "license-ok-button" - }]; - - CreateDialog({ - scope: scope, - buttons: buttons, - width: 675, - height: (IsAdmin()) ? 745 : 600, - minWidth: 400, - title: 'Ansible Tower License', - id: 'license-modal-dialog', - clonseOnEscape: false, - onClose: function() { - if (scope.codeMirror) { - scope.codeMirror.destroy(); - } - $('#license-modal-dialog').empty(); - }, - onResizeStop: function() { - if (IsAdmin()) { - TextareaResize({ - scope: scope, - textareaId: 'license_license_json', - modalId: 'license-modal-dialog', - formId: 'license-notification-body', - fld: 'license_json', - bottom_margin: 90, - parse: true, - onChange: function() { scope.license_json_api_error = ''; } - }); - } - }, - onOpen: function() { - if (IsAdmin()) { - setTimeout(function() { - TextareaResize({ - scope: scope, - textareaId: 'license_license_json', - modalId: 'license-modal-dialog', - formId: 'license-notification-body', - fld: 'license_json', - parse: true, - bottom_margin: 90, - onChange: function() { scope.license_json_api_error = ''; } - }); - }, 300); - } - $('#license-ok-button').focus(); - $('#update_license_link').on('click', function() { - if (IsAdmin()) { - TextareaResize({ - scope: scope, - textareaId: 'license_license_json', - modalId: 'license-modal-dialog', - formId: 'license-notification-body', - fld: 'license_json', - bottom_margin: 90, - parse: true, - onChange: function() { scope.license_json_api_error = ''; } - }); + for (fld in self.form.fields) { + if (fld !== 'time_remaining' && fld !== 'license_status' && fld !== 'tower_version') { + if (Empty(license_info[fld])) { + delete self.form.fields[fld]; } - }); - }, - callback: 'LicenseDialogReady' - }); - }, - - getDefaultHTML: function(license_info) { - var fld, html, - self = this, - generator = GenerateForm; - - self.form = angular.copy(LicenseForm); - - for (fld in self.form.fields) { - if (fld !== 'time_remaining' && fld !== 'license_status' && fld !== 'tower_version') { - if (Empty(license_info[fld])) { - delete self.form.fields[fld]; } } - } - if (!IsAdmin()) { - delete self.form.fields.license_key; - } - - if (license_info.is_aws || Empty(license_info.license_date)) { - delete self.form.fields.license_date; - delete self.form.fields.time_remaining; - } - - html = generator.buildHTML(self.form, { mode: 'edit', showButtons: false, breadCrumbs: false }); - return html; - }, - - loadDefaultScope: function(license_info, version) { - var fld, dt, days, license, - self = this, - scope = this.getScope(); - - for (fld in self.form.fields) { - if (!Empty(license_info[fld])) { - scope[fld] = license_info[fld]; + if (!IsAdmin()) { + delete self.form.fields.license_key; } - } - scope.tower_version = version; + if (license_info.is_aws || Empty(license_info.license_date)) { + delete self.form.fields.license_date; + delete self.form.fields.time_remaining; + } - if (scope.license_date) { - dt = new Date(parseInt(scope.license_date, 10) * 1000); // expects license_date in seconds - scope.license_date = FormatDate(dt); - scope.time_remaining = parseInt(scope.time_remaining,10) * 1000; - if (scope.time_remaining < 0) { - days = 0; + html = generator.buildHTML(self.form, { mode: 'edit', showButtons: false, breadCrumbs: false }); + return html; + }, + + loadDefaultScope: function(license_info, version) { + var fld, dt, days, license, + self = this, + scope = this.getScope(); + + for (fld in self.form.fields) { + if (!Empty(license_info[fld])) { + scope[fld] = license_info[fld]; + } + } + + scope.tower_version = version; + + if (scope.license_date) { + dt = new Date(parseInt(scope.license_date, 10) * 1000); // expects license_date in seconds + scope.license_date = FormatDate(dt); + scope.time_remaining = parseInt(scope.time_remaining,10) * 1000; + if (scope.time_remaining < 0) { + days = 0; + } else { + days = Math.floor(scope.time_remaining / 86400000); + } + scope.time_remaining = (days!==1) ? $filter('number')(days, 0) + ' days' : $filter('number')(days, 0) + ' day'; // '1 day' and '0 days/2 days' or more + } + + if (parseInt(scope.free_instances) <= 0) { + scope.free_instances_class = 'field-failure'; } else { - days = Math.floor(scope.time_remaining / 86400000); + scope.free_instances_class = 'field-success'; } - scope.time_remaining = (days!==1) ? $filter('number')(days, 0) + ' days' : $filter('number')(days, 0) + ' day'; // '1 day' and '0 days/2 days' or more - } - if (parseInt(scope.free_instances) <= 0) { - scope.free_instances_class = 'field-failure'; - } else { - scope.free_instances_class = 'field-success'; - } - - license = license_info; - if (license.valid_key === undefined) { - scope.license_status = 'Missing License Key'; - scope.status_color = 'license-invalid'; - } else if (!license.valid_key) { - scope.license_status = 'Invalid License Key'; - scope.status_color = 'license-invalid'; - } else if (license.date_expired !== undefined && license.date_expired) { - scope.license_status = 'License Expired'; - scope.status_color = 'license-expired'; - } else if (license.date_warning !== undefined && license.date_warning) { - scope.license_status = 'License Expiring Soon'; - scope.status_color = 'license-warning'; - } else if (license.free_instances !== undefined && parseInt(license.free_instances) <= 0) { - scope.license_status = 'No Available Managed Hosts'; - scope.status_color = 'license-invalid'; - } else { - scope.license_status = 'Valid License'; - scope.status_color = 'license-valid'; - } - }, - - getScope: function() { - return this.scope; - }, - - setLicense: function(license_info, version) { - this.license = license_info; - this.version = version; - }, - - getLicense: function() { - return this.license; - }, - - showViewer: function() { - var self = this, - scope = self.scope = $rootScope.$new(); - - if (scope.removeLicenseDataReady) { - scope.removeLicenseDataReady(); - } - scope.removeLicenseDataReady = scope.$on('LicenseDataReady', function(e, data) { - var html, version; - version = data.version.replace(/-.*$/,''); - self.setLicense(data.license_info, version); - html = self.getDefaultHTML(data.license_info); - self.loadDefaultScope(data.license_info, version); - self.eula = (data.eula) ? data.eula : "" ; - self.createDialog(html); - }); - CheckLicense.GetLicense('LicenseDataReady', scope); - } - }; -}]); - -/* -.factory('CheckLicense', ['$rootScope', 'Store', 'Alert', '$location', 'Authorization', - function ($rootScope, Store, Alert, $location, Authorization) { - return function () { - // Check license status and alert the user, if needed - var status = 'success', - hdr, msg, - license = Store('license'), - purchase_msg = '

    To purchase a license or extend an existing license ' + - 'visit the Ansible online store, ' + - 'or visit support.ansible.com for assistance.

    '; - if (license && !Authorization.licenseTested()) { - // This is our first time evaluating the license - license.tested = true; - Store('license',license); //update with tested flag - $rootScope.license_tested = true; - $rootScope.version = license.version; - if (license.valid_key !== undefined && license.valid_key === false) { - // The license is invalid. Stop the user from logging in. - status = 'alert-danger'; - hdr = 'License Error'; - msg = '

    There is a problem with the /etc/tower/license file on your Tower server. Check to make sure Tower can access ' + - 'the file.

    ' + purchase_msg; - Alert(hdr, msg, status, null, false, true); - } else if (license.demo !== undefined && license.demo === true) { - // demo - status = 'alert-info'; - hdr = 'Tower Demo'; - msg = '

    Thank you for trying Ansible Tower. You can use this edition to manage up to 10 hosts free.

    ' + - purchase_msg; - Alert(hdr, msg, status); + license = license_info; + if (license.valid_key === undefined) { + scope.license_status = 'Missing License Key'; + scope.status_color = 'license-invalid'; + } else if (!license.valid_key) { + scope.license_status = 'Invalid License Key'; + scope.status_color = 'license-invalid'; + } else if (license.date_expired !== undefined && license.date_expired) { + scope.license_status = 'License Expired'; + scope.status_color = 'license-expired'; + } else if (license.date_warning !== undefined && license.date_warning) { + scope.license_status = 'License Expiring Soon'; + scope.status_color = 'license-warning'; + } else if (license.free_instances !== undefined && parseInt(license.free_instances) <= 0) { + scope.license_status = 'No Available Managed Hosts'; + scope.status_color = 'license-invalid'; + } else { + scope.license_status = 'Valid License'; + scope.status_color = 'license-valid'; } - if (license.date_expired !== undefined && license.date_expired === true) { - // expired - status = 'alert-info'; - hdr = 'License Expired'; - msg = '

    Your Ansible Tower License has expired and is no longer compliant. You can continue, but you will be ' + - 'unable to add any additional hosts.

    ' + purchase_msg; - Alert(hdr, msg, status); - } else if (license.date_warning !== undefined && license.date_warning === true) { - status = 'alert-info'; - hdr = 'License Warning'; - msg = '

    Your Ansible Tower license is about to expire!

    ' + purchase_msg; - Alert(hdr, msg, status); - } - if (license.free_instances !== undefined && parseInt(license.free_instances) <= 0) { - status = 'alert-info'; - hdr = 'License Warning'; - msg = '

    Your Ansible Tower license has reached capacity for the number of managed ' + - 'hosts allowed. You will not be able to add any additional hosts.

    ' + purchase_msg; - Alert(hdr, msg, status, null, true); + }, + + getScope: function() { + return this.scope; + }, + + setLicense: function(license_info, version) { + this.license = license_info; + this.version = version; + }, + + getLicense: function() { + return this.license; + }, + + showViewer: function() { + var self = this, + scope = self.scope = $rootScope.$new(); + + if (scope.removeLicenseDataReady) { + scope.removeLicenseDataReady(); } + scope.removeLicenseDataReady = scope.$on('LicenseDataReady', function(e, data) { + var html, version; + version = data.version.replace(/-.*$/,''); + self.setLicense(data.license_info, version); + html = self.getDefaultHTML(data.license_info); + self.loadDefaultScope(data.license_info, version); + self.eula = (data.eula) ? data.eula : "" ; + self.createDialog(html); + }); + CheckLicense.GetLicense('LicenseDataReady', scope); } }; - } -]); -*/ + }]); + /* + .factory('CheckLicense', ['$rootScope', 'Store', 'Alert', '$location', 'Authorization', + function ($rootScope, Store, Alert, $location, Authorization) { + return function () { + // Check license status and alert the user, if needed + var status = 'success', + hdr, msg, + license = Store('license'), + purchase_msg = '

    To purchase a license or extend an existing license ' + + 'visit the Ansible online store, ' + + 'or visit support.ansible.com for assistance.

    '; + if (license && !Authorization.licenseTested()) { + // This is our first time evaluating the license + license.tested = true; + Store('license',license); //update with tested flag + $rootScope.license_tested = true; + $rootScope.version = license.version; + if (license.valid_key !== undefined && license.valid_key === false) { + // The license is invalid. Stop the user from logging in. + status = 'alert-danger'; + hdr = 'License Error'; + msg = '

    There is a problem with the /etc/tower/license file on your Tower server. Check to make sure Tower can access ' + + 'the file.

    ' + purchase_msg; + Alert(hdr, msg, status, null, false, true); + } else if (license.demo !== undefined && license.demo === true) { + // demo + status = 'alert-info'; + hdr = 'Tower Demo'; + msg = '

    Thank you for trying Ansible Tower. You can use this edition to manage up to 10 hosts free.

    ' + + purchase_msg; + Alert(hdr, msg, status); + } + if (license.date_expired !== undefined && license.date_expired === true) { + // expired + status = 'alert-info'; + hdr = 'License Expired'; + msg = '

    Your Ansible Tower License has expired and is no longer compliant. You can continue, but you will be ' + + 'unable to add any additional hosts.

    ' + purchase_msg; + Alert(hdr, msg, status); + } else if (license.date_warning !== undefined && license.date_warning === true) { + status = 'alert-info'; + hdr = 'License Warning'; + msg = '

    Your Ansible Tower license is about to expire!

    ' + purchase_msg; + Alert(hdr, msg, status); + } + if (license.free_instances !== undefined && parseInt(license.free_instances) <= 0) { + status = 'alert-info'; + hdr = 'License Warning'; + msg = '

    Your Ansible Tower license has reached capacity for the number of managed ' + + 'hosts allowed. You will not be able to add any additional hosts.

    ' + purchase_msg; + Alert(hdr, msg, status, null, true); + } + } + }; + } + ]); + */ diff --git a/awx/ui/static/js/helpers/LoadConfig.js b/awx/ui/static/js/helpers/LoadConfig.js index da872c6672..90bf5428bb 100644 --- a/awx/ui/static/js/helpers/LoadConfig.js +++ b/awx/ui/static/js/helpers/LoadConfig.js @@ -16,6 +16,7 @@ +export default angular.module('LoadConfigHelper', ['Utilities']) .factory('LoadConfig', ['$log', '$rootScope', '$http', 'ProcessErrors', 'Store', function($log, $rootScope, $http, ProcessErrors, Store) { diff --git a/awx/ui/static/js/helpers/LogViewer.js b/awx/ui/static/js/helpers/LogViewer.js index bbd56f3bd7..88b548162c 100644 --- a/awx/ui/static/js/helpers/LogViewer.js +++ b/awx/ui/static/js/helpers/LogViewer.js @@ -10,373 +10,373 @@ * @description logviewer */ +export default + angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', 'VariablesHelper']) -angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', 'VariablesHelper']) + .factory('LogViewer', ['$location', '$compile', 'CreateDialog', 'GetJob', 'Wait', 'GenerateForm', 'LogViewerStatusForm', 'AddTable', 'AddTextarea', + 'LogViewerOptionsForm', 'EnvTable', 'GetBasePath', 'LookUpName', 'Empty', 'AddPreFormattedText', 'ParseVariableString', 'GetChoices', + function($location, $compile, CreateDialog, GetJob, Wait, GenerateForm, LogViewerStatusForm, AddTable, AddTextarea, LogViewerOptionsForm, EnvTable, + GetBasePath, LookUpName, Empty, AddPreFormattedText, ParseVariableString, GetChoices) { + return function(params) { + var parent_scope = params.scope, + url = params.url, + getIcon = params.getIcon, + scope = parent_scope.$new(true), + base = $location.path().replace(/^\//, '').split('/')[0], + pieces; - .factory('LogViewer', ['$location', '$compile', 'CreateDialog', 'GetJob', 'Wait', 'GenerateForm', 'LogViewerStatusForm', 'AddTable', 'AddTextarea', - 'LogViewerOptionsForm', 'EnvTable', 'GetBasePath', 'LookUpName', 'Empty', 'AddPreFormattedText', 'ParseVariableString', 'GetChoices', - function($location, $compile, CreateDialog, GetJob, Wait, GenerateForm, LogViewerStatusForm, AddTable, AddTextarea, LogViewerOptionsForm, EnvTable, - GetBasePath, LookUpName, Empty, AddPreFormattedText, ParseVariableString, GetChoices) { - return function(params) { - var parent_scope = params.scope, - url = params.url, - getIcon = params.getIcon, - scope = parent_scope.$new(true), - base = $location.path().replace(/^\//, '').split('/')[0], - pieces; - - if (scope.removeModalReady) { - scope.removeModalReady(); - } - scope.removeModalReady = scope.$on('ModalReady', function() { - Wait('stop'); - $('#logviewer-modal-dialog').dialog('open'); - }); - - if (scope.removeJobReady) { - scope.removeJobReady(); - } - scope.removeJobReady = scope.$on('JobReady', function(e, data) { - var key, resizeText, elem; - $('#status-form-container').empty(); - $('#options-form-container').empty(); - $('#stdout-form-container').empty(); - $('#traceback-form-container').empty(); - $('#variables-container').empty(); - $('#source-container').empty(); - $('#logview-tabs li:eq(1)').hide(); - $('#logview-tabs li:eq(2)').hide(); - $('#logview-tabs li:eq(4)').hide(); - $('#logview-tabs li:eq(5)').hide(); - - // Make sure subsequenct scope references don't bubble up to the parent - for (key in LogViewerStatusForm.fields) { - scope[key] = ''; - } - for (key in LogViewerOptionsForm.fields) { - scope[key] = ''; + if (scope.removeModalReady) { + scope.removeModalReady(); } + scope.removeModalReady = scope.$on('ModalReady', function() { + Wait('stop'); + $('#logviewer-modal-dialog').dialog('open'); + }); - for (key in data) { - scope[key] = data[key]; + if (scope.removeJobReady) { + scope.removeJobReady(); } - scope.created_by = ''; - scope.job_template = ''; + scope.removeJobReady = scope.$on('JobReady', function(e, data) { + var key, resizeText, elem; + $('#status-form-container').empty(); + $('#options-form-container').empty(); + $('#stdout-form-container').empty(); + $('#traceback-form-container').empty(); + $('#variables-container').empty(); + $('#source-container').empty(); + $('#logview-tabs li:eq(1)').hide(); + $('#logview-tabs li:eq(2)').hide(); + $('#logview-tabs li:eq(4)').hide(); + $('#logview-tabs li:eq(5)').hide(); - if (data.related.created_by) { - pieces = data.related.created_by.replace(/^\//,'').replace(/\/$/,'').split('/'); - scope.created_by = parseInt(pieces[pieces.length - 1],10); - LookUpName({ - scope: scope, - scope_var: 'created_by', - url: GetBasePath('users') + scope.created_by + '/' - }); - } - - // For jobs link the name to the job parent - if (base === 'jobs') { - if (data.type === 'job') { - scope.name_link = "job_template"; - scope.job_template = data.unified_job_template; - scope.job_template_name = (data.summary_fields.job_template) ? data.summary_fields.job_template.name : data.name; - scope.name_id = data.unified_job_template; + // Make sure subsequenct scope references don't bubble up to the parent + for (key in LogViewerStatusForm.fields) { + scope[key] = ''; } - if (data.type === 'project_update') { - scope.name_link = "project"; - scope.name_id = data.unified_job_template; + for (key in LogViewerOptionsForm.fields) { + scope[key] = ''; } - if (data.type === 'inventory_update') { - scope.name_link = "inventory_source"; - scope.name_id = scope.group; + + for (key in data) { + scope[key] = data[key]; } - } + scope.created_by = ''; + scope.job_template = ''; - AddTable({ scope: scope, form: LogViewerStatusForm, id: 'status-form-container', getIcon: getIcon }); - AddTable({ scope: scope, form: LogViewerOptionsForm, id: 'options-form-container', getIcon: getIcon }); - - if (data.result_stdout) { - $('#logview-tabs li:eq(1)').show(); - AddPreFormattedText({ - id: 'stdout-form-container', - val: data.result_stdout - }); - } - - if (data.result_traceback) { - $('#logview-tabs li:eq(2)').show(); - AddPreFormattedText({ - id: 'traceback-form-container', - val: data.result_traceback - }); - } - - /*if (data.job_env) { - EnvTable({ - id: 'env-form-container', - vars: data.job_env - }); - }*/ - - if (data.extra_vars) { - $('#logview-tabs li:eq(4)').show(); - AddTextarea({ - container_id: 'variables-container', - fld_id: 'variables', - val: ParseVariableString(data.extra_vars) - }); - } - - if (data.source_vars) { - $('#logview-tabs li:eq(5)').show(); - AddTextarea({ - container_id: 'source-container', - fld_id: 'source-variables', - val: ParseVariableString(data.source_vars) - }); - } - - if (!Empty(scope.source)) { - if (scope.removeChoicesReady) { - scope.removeChoicesReady(); - } - scope.removeChoicesReady = scope.$on('ChoicesReady', function() { - scope.source_choices.every(function(e) { - if (e.value === scope.source) { - scope.source = e.label; - return false; - } - return true; + if (data.related.created_by) { + pieces = data.related.created_by.replace(/^\//,'').replace(/\/$/,'').split('/'); + scope.created_by = parseInt(pieces[pieces.length - 1],10); + LookUpName({ + scope: scope, + scope_var: 'created_by', + url: GetBasePath('users') + scope.created_by + '/' }); - }); - GetChoices({ - scope: scope, - url: GetBasePath('inventory_sources'), - field: 'source', - variable: 'source_choices', - choice_name: 'choices', - callback: 'ChoicesReady' - }); - } + } - if (!Empty(scope.credential)) { - LookUpName({ - scope: scope, - scope_var: 'credential', - url: GetBasePath('credentials') + scope.credential + '/' - }); - } + // For jobs link the name to the job parent + if (base === 'jobs') { + if (data.type === 'job') { + scope.name_link = "job_template"; + scope.job_template = data.unified_job_template; + scope.job_template_name = (data.summary_fields.job_template) ? data.summary_fields.job_template.name : data.name; + scope.name_id = data.unified_job_template; + } + if (data.type === 'project_update') { + scope.name_link = "project"; + scope.name_id = data.unified_job_template; + } + if (data.type === 'inventory_update') { + scope.name_link = "inventory_source"; + scope.name_id = scope.group; + } + } - if (!Empty(scope.inventory)) { - LookUpName({ - scope: scope, - scope_var: 'inventory', - url: GetBasePath('inventory') + scope.inventory + '/' - }); - } + AddTable({ scope: scope, form: LogViewerStatusForm, id: 'status-form-container', getIcon: getIcon }); + AddTable({ scope: scope, form: LogViewerOptionsForm, id: 'options-form-container', getIcon: getIcon }); - if (!Empty(scope.project)) { - LookUpName({ - scope: scope, - scope_var: 'project', - url: GetBasePath('projects') + scope.project + '/' - }); - } + if (data.result_stdout) { + $('#logview-tabs li:eq(1)').show(); + AddPreFormattedText({ + id: 'stdout-form-container', + val: data.result_stdout + }); + } - if (!Empty(scope.cloud_credential)) { - LookUpName({ - scope: scope, - scope_var: 'cloud_credential', - url: GetBasePath('credentials') + scope.cloud_credential + '/' - }); - } + if (data.result_traceback) { + $('#logview-tabs li:eq(2)').show(); + AddPreFormattedText({ + id: 'traceback-form-container', + val: data.result_traceback + }); + } - if (!Empty(scope.inventory_source)) { - LookUpName({ - scope: scope, - scope_var: 'inventory_source', - url: GetBasePath('inventory_sources') + scope.inventory_source + '/' - }); - } + /*if (data.job_env) { + EnvTable({ + id: 'env-form-container', + vars: data.job_env + }); + }*/ - resizeText = function() { - var u = $('#logview-tabs').outerHeight() + 25, - h = $('#logviewer-modal-dialog').innerHeight(), - rows = Math.floor((h - u) / 20); - rows -= 3; - rows = (rows < 6) ? 6 : rows; - $('#logviewer-modal-dialog #variables').attr({ rows: rows }); - $('#logviewer-modal-dialog #source-variables').attr({ rows: rows }); + if (data.extra_vars) { + $('#logview-tabs li:eq(4)').show(); + AddTextarea({ + container_id: 'variables-container', + fld_id: 'variables', + val: ParseVariableString(data.extra_vars) + }); + } + + if (data.source_vars) { + $('#logview-tabs li:eq(5)').show(); + AddTextarea({ + container_id: 'source-container', + fld_id: 'source-variables', + val: ParseVariableString(data.source_vars) + }); + } + + if (!Empty(scope.source)) { + if (scope.removeChoicesReady) { + scope.removeChoicesReady(); + } + scope.removeChoicesReady = scope.$on('ChoicesReady', function() { + scope.source_choices.every(function(e) { + if (e.value === scope.source) { + scope.source = e.label; + return false; + } + return true; + }); + }); + GetChoices({ + scope: scope, + url: GetBasePath('inventory_sources'), + field: 'source', + variable: 'source_choices', + choice_name: 'choices', + callback: 'ChoicesReady' + }); + } + + if (!Empty(scope.credential)) { + LookUpName({ + scope: scope, + scope_var: 'credential', + url: GetBasePath('credentials') + scope.credential + '/' + }); + } + + if (!Empty(scope.inventory)) { + LookUpName({ + scope: scope, + scope_var: 'inventory', + url: GetBasePath('inventory') + scope.inventory + '/' + }); + } + + if (!Empty(scope.project)) { + LookUpName({ + scope: scope, + scope_var: 'project', + url: GetBasePath('projects') + scope.project + '/' + }); + } + + if (!Empty(scope.cloud_credential)) { + LookUpName({ + scope: scope, + scope_var: 'cloud_credential', + url: GetBasePath('credentials') + scope.cloud_credential + '/' + }); + } + + if (!Empty(scope.inventory_source)) { + LookUpName({ + scope: scope, + scope_var: 'inventory_source', + url: GetBasePath('inventory_sources') + scope.inventory_source + '/' + }); + } + + resizeText = function() { + var u = $('#logview-tabs').outerHeight() + 25, + h = $('#logviewer-modal-dialog').innerHeight(), + rows = Math.floor((h - u) / 20); + rows -= 3; + rows = (rows < 6) ? 6 : rows; + $('#logviewer-modal-dialog #variables').attr({ rows: rows }); + $('#logviewer-modal-dialog #source-variables').attr({ rows: rows }); + }; + + elem = angular.element(document.getElementById('logviewer-modal-dialog')); + $compile(elem)(scope); + + CreateDialog({ + scope: scope, + width: 600, + height: 550, + minWidth: 450, + callback: 'ModalReady', + id: 'logviewer-modal-dialog', + onResizeStop: resizeText, + title: 'Job Results', + onOpen: function() { + $('#logview-tabs a:first').tab('show'); + $('#dialog-ok-button').focus(); + resizeText(); + } + }); + }); + + GetJob({ + url: url, + scope: scope + }); + + scope.modalOK = function() { + $('#logviewer-modal-dialog').dialog('close'); + scope.$destroy(); }; - - elem = angular.element(document.getElementById('logviewer-modal-dialog')); - $compile(elem)(scope); - - CreateDialog({ - scope: scope, - width: 600, - height: 550, - minWidth: 450, - callback: 'ModalReady', - id: 'logviewer-modal-dialog', - onResizeStop: resizeText, - title: 'Job Results', - onOpen: function() { - $('#logview-tabs a:first').tab('show'); - $('#dialog-ok-button').focus(); - resizeText(); - } - }); - }); - - GetJob({ - url: url, - scope: scope - }); - - scope.modalOK = function() { - $('#logviewer-modal-dialog').dialog('close'); - scope.$destroy(); }; - }; - }]) + }]) - .factory('GetJob', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { - return function(params) { - var url = params.url, - scope = params.scope; - Rest.setUrl(url); - Rest.get() - .success(function(data){ - scope.$emit('JobReady', data); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve ' + url + '. GET returned: ' + status }); - }); - }; - }]) + .factory('GetJob', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { + return function(params) { + var url = params.url, + scope = params.scope; + Rest.setUrl(url); + Rest.get() + .success(function(data){ + scope.$emit('JobReady', data); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve ' + url + '. GET returned: ' + status }); + }); + }; + }]) - .factory('LookUpName', ['Rest', 'ProcessErrors', 'Empty', function(Rest, ProcessErrors, Empty) { - return function(params) { - var url = params.url, - scope_var = params.scope_var, - scope = params.scope; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - if (scope_var === 'inventory_source') { - scope[scope_var + '_name'] = data.summary_fields.group.name; - } - else if (!Empty(data.name)) { - scope[scope_var + '_name'] = data.name; - } - if (!Empty(data.group)) { - // Used for inventory_source - scope.group = data.group; - } - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve ' + url + '. GET returned: ' + status }); - }); - }; - }]) + .factory('LookUpName', ['Rest', 'ProcessErrors', 'Empty', function(Rest, ProcessErrors, Empty) { + return function(params) { + var url = params.url, + scope_var = params.scope_var, + scope = params.scope; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + if (scope_var === 'inventory_source') { + scope[scope_var + '_name'] = data.summary_fields.group.name; + } + else if (!Empty(data.name)) { + scope[scope_var + '_name'] = data.name; + } + if (!Empty(data.group)) { + // Used for inventory_source + scope.group = data.group; + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve ' + url + '. GET returned: ' + status }); + }); + }; + }]) - .factory('AddTable', ['$compile', 'Empty', 'Find', function($compile, Empty, Find) { - return function(params) { - var form = params.form, - id = params.id, - scope = params.scope, - getIcon = params.getIcon, - fld, html, url, e, - urls = [ - { "variable": "credential", "url": "/#/credentials/" }, - { "variable": "project", "url": "/#/projects/" }, - { "variable": "inventory", "url": "/#/inventories/" }, - { "variable": "cloud_credential", "url": "/#/credentials/" }, - { "variable": "inventory_source", "url": "/#/home/groups/?id={{ group }}" }, - { "variable": "job_template", "url": "/#/job_templates/" }, - { "variable": "created_by", "url": "/#/users/" } - ]; - html = "
    \n"; - for (fld in form.fields) { - if (!Empty(scope[fld])) { - html += "" + - "
    " + form.fields[fld].label + ""; - url = Find({ list: urls, key: "variable", val: fld }); - if (url) { - html += "{{ " + fld + '_name' + " }}"; - } - else if (fld === 'name' && scope.name_link) { - url = Find({ list: urls, key: "variable", val: scope.name_link }); - html += "{{ " + - ( (scope.name_link === 'inventory_source') ? 'inventory_source_name' : fld ) + " }}"; - } - else if (fld === 'elapsed') { - html += scope[fld] + " seconds"; - } - else if (fld === 'status') { - if (getIcon) { - html += " " + scope[fld]; + .factory('AddTable', ['$compile', 'Empty', 'Find', function($compile, Empty, Find) { + return function(params) { + var form = params.form, + id = params.id, + scope = params.scope, + getIcon = params.getIcon, + fld, html, url, e, + urls = [ + { "variable": "credential", "url": "/#/credentials/" }, + { "variable": "project", "url": "/#/projects/" }, + { "variable": "inventory", "url": "/#/inventories/" }, + { "variable": "cloud_credential", "url": "/#/credentials/" }, + { "variable": "inventory_source", "url": "/#/home/groups/?id={{ group }}" }, + { "variable": "job_template", "url": "/#/job_templates/" }, + { "variable": "created_by", "url": "/#/users/" } + ]; + html = "\n"; + for (fld in form.fields) { + if (!Empty(scope[fld])) { + html += "" + + "\n"; } - else { - html += "{{ " + fld ; - html += (form.fields[fld].filter) ? " | " + form.fields[fld].filter : "" ; - html += " }}"; - } - html += "\n"; } - } - html += "
    " + form.fields[fld].label + ""; + url = Find({ list: urls, key: "variable", val: fld }); + if (url) { + html += "{{ " + fld + '_name' + " }}"; + } + else if (fld === 'name' && scope.name_link) { + url = Find({ list: urls, key: "variable", val: scope.name_link }); + html += "{{ " + + ( (scope.name_link === 'inventory_source') ? 'inventory_source_name' : fld ) + " }}"; + } + else if (fld === 'elapsed') { + html += scope[fld] + " seconds"; + } + else if (fld === 'status') { + if (getIcon) { + html += " " + scope[fld]; + } + else { + html += " " + scope[fld]; + } + if (scope.job_explanation) { + html += "

    " + scope.job_explanation + "

    "; + } } else { - html += " " + scope[fld]; - } - if (scope.job_explanation) { - html += "

    " + scope.job_explanation + "

    "; + html += "{{ " + fld ; + html += (form.fields[fld].filter) ? " | " + form.fields[fld].filter : "" ; + html += " }}"; } + html += "
    \n"; - e = angular.element(document.getElementById(id)); - e.empty().html(html); - $compile(e)(scope); - }; - }]) + html += "
    \n"; + e = angular.element(document.getElementById(id)); + e.empty().html(html); + $compile(e)(scope); + }; + }]) - .factory('AddTextarea', [ function() { - return function(params) { - var container_id = params.container_id, - val = params.val, - fld_id = params.fld_id, - html; - html = "
    \n" + - "" + - "
    \n"; - $('#' + container_id).empty().html(html); - }; - }]) + .factory('AddTextarea', [ function() { + return function(params) { + var container_id = params.container_id, + val = params.val, + fld_id = params.fld_id, + html; + html = "
    \n" + + "" + + "
    \n"; + $('#' + container_id).empty().html(html); + }; + }]) - .factory('AddPreFormattedText', [function() { - return function(params) { - var id = params.id, - val = params.val, - html; - html = "
    " + val + "
    \n"; - $('#' + id).empty().html(html); - }; - }]) + .factory('AddPreFormattedText', [function() { + return function(params) { + var id = params.id, + val = params.val, + html; + html = "
    " + val + "
    \n"; + $('#' + id).empty().html(html); + }; + }]) - .factory('EnvTable', [ function() { - return function(params) { - var id = params.id, - vars = params.vars, - key, html; - html = "\n"; - for (key in vars) { - html += "" + - "\n"; - } - html += "
    " + key + "" + vars[key] + "
    \n"; - $('#' + id).empty().html(html); - }; - }]); \ No newline at end of file + .factory('EnvTable', [ function() { + return function(params) { + var id = params.id, + vars = params.vars, + key, html; + html = "\n"; + for (key in vars) { + html += "" + + "\n"; + } + html += "
    " + key + "" + vars[key] + "
    \n"; + $('#' + id).empty().html(html); + }; + }]); diff --git a/awx/ui/static/js/helpers/Lookup.js b/awx/ui/static/js/helpers/Lookup.js index 9e62a3249a..d0384247ab 100644 --- a/awx/ui/static/js/helpers/Lookup.js +++ b/awx/ui/static/js/helpers/Lookup.js @@ -23,228 +23,228 @@ */ +export default + angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ApiLoader', 'ModalDialog']) -angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ApiLoader', 'ModalDialog']) + .factory('LookUpInit', ['Alert', 'Rest', 'GenerateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty', 'CreateDialog', + function (Alert, Rest, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty, CreateDialog) { + return function (params) { - .factory('LookUpInit', ['Alert', 'Rest', 'GenerateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty', 'CreateDialog', - function (Alert, Rest, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty, CreateDialog) { - return function (params) { + var parent_scope = params.scope, + form = params.form, + list = params.list, + field = params.field, + instructions = params.instructions, + postAction = params.postAction, + callback = params.callback, + input_type = (params.input_type) ? params.input_type: "checkbox", + defaultUrl, name, watchUrl; - var parent_scope = params.scope, - form = params.form, - list = params.list, - field = params.field, - instructions = params.instructions, - postAction = params.postAction, - callback = params.callback, - input_type = (params.input_type) ? params.input_type: "checkbox", - defaultUrl, name, watchUrl; + if (params.url) { + // pass in a url value to override the default + defaultUrl = params.url; + } else { + defaultUrl = (list.name === 'inventories') ? GetBasePath('inventory') : GetBasePath(list.name); + } - if (params.url) { - // pass in a url value to override the default - defaultUrl = params.url; - } else { - defaultUrl = (list.name === 'inventories') ? GetBasePath('inventory') : GetBasePath(list.name); - } - - if ($('#htmlTemplate #lookup-modal-dialog').length > 0) { - $('#htmlTemplate #lookup-modal-dialog').empty(); - } - else { - $('#htmlTemplate').append("
    "); - } - - name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1); - - watchUrl = (/\/$/.test(defaultUrl)) ? defaultUrl + '?' : defaultUrl + '&'; - watchUrl += form.fields[field].sourceField + '__' + 'iexact=:value'; - - $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-url', watchUrl); - $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-source', field); - - - parent_scope['lookUp' + name] = function () { - - var master = {}, - scope = parent_scope.$new(), - name, hdr, buttons; - - // Generating the search list potentially kills the values held in scope for the field. - // We'll keep a copy in master{} that we can revert back to on cancel; - master[field] = scope[field]; - master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = - scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; - - GenerateList.inject(list, { - mode: 'lookup', - id: 'lookup-modal-dialog', - scope: scope, - instructions: instructions, - input_type: input_type - }); + if ($('#htmlTemplate #lookup-modal-dialog').length > 0) { + $('#htmlTemplate #lookup-modal-dialog').empty(); + } + else { + $('#htmlTemplate').append("
    "); + } name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1); - hdr = (params.hdr) ? params.hdr : 'Select ' + name; - // Show pop-up - buttons = [{ - label: "Cancel", - icon: "fa-times", - "class": "btn btn-default", - "id": "lookup-cancel-button", - onClick: function() { - $('#lookup-modal-dialog').dialog('close'); + watchUrl = (/\/$/.test(defaultUrl)) ? defaultUrl + '?' : defaultUrl + '&'; + watchUrl += form.fields[field].sourceField + '__' + 'iexact=:value'; + + $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-url', watchUrl); + $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-source', field); + + + parent_scope['lookUp' + name] = function () { + + var master = {}, + scope = parent_scope.$new(), + name, hdr, buttons; + + // Generating the search list potentially kills the values held in scope for the field. + // We'll keep a copy in master{} that we can revert back to on cancel; + master[field] = scope[field]; + master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = + scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; + + GenerateList.inject(list, { + mode: 'lookup', + id: 'lookup-modal-dialog', + scope: scope, + instructions: instructions, + input_type: input_type + }); + + name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1); + hdr = (params.hdr) ? params.hdr : 'Select ' + name; + + // Show pop-up + buttons = [{ + label: "Cancel", + icon: "fa-times", + "class": "btn btn-default", + "id": "lookup-cancel-button", + onClick: function() { + $('#lookup-modal-dialog').dialog('close'); + } + },{ + label: "Select", + onClick: function() { + scope.selectAction(); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "lookup-save-button" + }]; + + if (scope.removeModalReady) { + scope.removeModalReady(); } - },{ - label: "Select", - onClick: function() { - scope.selectAction(); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "lookup-save-button" - }]; + scope.removeModalReady = scope.$on('ModalReady', function() { + $('#lookup-save-button').attr('disabled','disabled'); + $('#lookup-modal-dialog').dialog('open'); + }); - if (scope.removeModalReady) { - scope.removeModalReady(); - } - scope.removeModalReady = scope.$on('ModalReady', function() { - $('#lookup-save-button').attr('disabled','disabled'); - $('#lookup-modal-dialog').dialog('open'); - }); + CreateDialog({ + scope: scope, + buttons: buttons, + width: 600, + height: (instructions) ? 625 : 500, + minWidth: 500, + title: hdr, + id: 'lookup-modal-dialog', + onClose: function() { + setTimeout( function() { + scope.$apply( function() { + if (Empty(scope[field])) { + scope[field] = master[field]; + scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = + master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; + } + }); + }, 300); + }, + callback: 'ModalReady' + }); - CreateDialog({ - scope: scope, - buttons: buttons, - width: 600, - height: (instructions) ? 625 : 500, - minWidth: 500, - title: hdr, - id: 'lookup-modal-dialog', - onClose: function() { - setTimeout( function() { - scope.$apply( function() { - if (Empty(scope[field])) { - scope[field] = master[field]; - scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = - master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; + SearchInit({ + scope: scope, + set: list.name, + list: list, + url: defaultUrl + }); + + PaginateInit({ + scope: scope, + list: list, + url: defaultUrl, + mode: 'lookup' + }); + + if (scope.lookupPostRefreshRemove) { + scope.lookupPostRefreshRemove(); + } + scope.lookupPostRefreshRemove = scope.$on('PostRefresh', function () { + var fld, i; + for (fld in list.fields) { + if (list.fields[fld].type && list.fields[fld].type === 'date') { + //convert dates to our standard format + for (i = 0; i < scope[list.name].length; i++) { + scope[list.name][i][fld] = FormatDate(new Date(scope[list.name][i][fld])); + } + } + } + + // List generator creates the list, resetting it and losing the previously selected value. + // If the selected value is in the current set, find it and mark selected. + if (!Empty(parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField])) { + scope[list.name].forEach(function(elem) { + if (elem[form.fields[field].sourceField] === + parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { + scope[field] = elem.id; } }); - }, 300); - }, - callback: 'ModalReady' - }); - SearchInit({ - scope: scope, - set: list.name, - list: list, - url: defaultUrl - }); - - PaginateInit({ - scope: scope, - list: list, - url: defaultUrl, - mode: 'lookup' - }); - - if (scope.lookupPostRefreshRemove) { - scope.lookupPostRefreshRemove(); - } - scope.lookupPostRefreshRemove = scope.$on('PostRefresh', function () { - var fld, i; - for (fld in list.fields) { - if (list.fields[fld].type && list.fields[fld].type === 'date') { - //convert dates to our standard format - for (i = 0; i < scope[list.name].length; i++) { - scope[list.name][i][fld] = FormatDate(new Date(scope[list.name][i][fld])); - } } - } - // List generator creates the list, resetting it and losing the previously selected value. - // If the selected value is in the current set, find it and mark selected. - if (!Empty(parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField])) { - scope[list.name].forEach(function(elem) { - if (elem[form.fields[field].sourceField] === - parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { - scope[field] = elem.id; - } - }); + if (!Empty(scope[field])) { + scope['toggle_' + list.iterator](scope[field]); + } - } + }); - if (!Empty(scope[field])) { - scope['toggle_' + list.iterator](scope[field]); - } + scope.search(list.iterator); - }); - - scope.search(list.iterator); - - scope.selectAction = function () { - var i, found = false; - for (i = 0; i < scope[list.name].length; i++) { - if (scope[list.name][i].checked === '1' || scope[list.name][i].checked===1) { - found = true; - parent_scope[field] = scope[list.name][i].id; - if (parent_scope[form.name + '_form'] && form.fields[field] && form.fields[field].sourceModel) { - parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = - scope[list.name][i][form.fields[field].sourceField]; - if (parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { - parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField] - .$setValidity('awlookup', true); + scope.selectAction = function () { + var i, found = false; + for (i = 0; i < scope[list.name].length; i++) { + if (scope[list.name][i].checked === '1' || scope[list.name][i].checked===1) { + found = true; + parent_scope[field] = scope[list.name][i].id; + if (parent_scope[form.name + '_form'] && form.fields[field] && form.fields[field].sourceModel) { + parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = + scope[list.name][i][form.fields[field].sourceField]; + if (parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { + parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField] + .$setValidity('awlookup', true); + } + } + if (parent_scope[form.name + '_form']) { + parent_scope[form.name + '_form'].$setDirty(); } } - if (parent_scope[form.name + '_form']) { - parent_scope[form.name + '_form'].$setDirty(); + } + if (found) { + // Selection made + $('#lookup-modal-dialog').dialog('close'); + if (postAction) { + postAction(); + } + if (callback) { + parent_scope.$emit(callback); } } - } - if (found) { - // Selection made - $('#lookup-modal-dialog').dialog('close'); - if (postAction) { - postAction(); - } - if (callback) { - parent_scope.$emit(callback); - } - } - }; + }; - scope['toggle_' + list.iterator] = function (id) { - var count = 0; - scope[list.name].forEach( function(row, i) { - if (row.id === id) { - if (row.checked === '0') { - scope[list.name][i].checked = '1'; - scope[list.name][i].success_class = 'success'; - } - else { + scope['toggle_' + list.iterator] = function (id) { + var count = 0; + scope[list.name].forEach( function(row, i) { + if (row.id === id) { + if (row.checked === '0') { + 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 = ''; + } + } else { scope[list.name][i].checked = '0'; 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 === '1') { + count++; + } + }); + if (count === 0) { + $('#lookup-save-button').attr('disabled','disabled'); } - }); - // Check if any rows are checked - scope[list.name].forEach(function(row) { - if (row.checked === '1') { - count++; + else { + $('#lookup-save-button').removeAttr('disabled'); } - }); - if (count === 0) { - $('#lookup-save-button').attr('disabled','disabled'); - } - else { - $('#lookup-save-button').removeAttr('disabled'); - } + }; }; }; - }; - }]); + }]); diff --git a/awx/ui/static/js/helpers/PaginationHelpers.js b/awx/ui/static/js/helpers/PaginationHelpers.js index 9429664440..38eca49858 100644 --- a/awx/ui/static/js/helpers/PaginationHelpers.js +++ b/awx/ui/static/js/helpers/PaginationHelpers.js @@ -10,155 +10,155 @@ * @description pagination */ +export default + angular.module('PaginationHelpers', ['Utilities', 'RefreshHelper', 'RefreshRelatedHelper']) -angular.module('PaginationHelpers', ['Utilities', 'RefreshHelper', 'RefreshRelatedHelper']) + .factory('PageRangeSetup', ['Empty', + function (Empty) { + return function (params) { -.factory('PageRangeSetup', ['Empty', - function (Empty) { - return function (params) { - - var scope = params.scope, - count = params.count, - next = params.next, - previous = params.previous, - iterator = params.iterator, - i, first, last; - - scope[iterator + '_page'] = 1; - scope[iterator + '_num_pages'] = Math.ceil((count / scope[iterator + '_page_size'])); - scope[iterator + '_num_pages'] = (scope[iterator + '_num_pages'] <= 0) ? 1 : scope[iterator + '_num_pages']; - scope[iterator + '_total_rows'] = count; - - // Which page are we on? - if (Empty(next) && previous) { - // no next page, but there is a previous page - scope[iterator + '_page'] = parseInt(previous.match(/page=\d+/)[0].replace(/page=/, '')) + 1; - } else if (next && Empty(previous)) { - // next page available, but no previous page - scope[iterator + '_page'] = 1; - } else if (next && previous) { - // we're in between next and previous - scope[iterator + '_page'] = parseInt(previous.match(/page=\d+/)[0].replace(/page=/, '')) + 1; - } - - // Calc the range of up to 10 pages to show - scope[iterator + '_page_range'] = []; - first = (scope[iterator + '_page'] > 5) ? scope[iterator + '_page'] - 5 : 1; - if (scope[iterator + '_page'] < 6) { - last = (10 <= scope[iterator + '_num_pages']) ? 10 : scope[iterator + '_num_pages']; - } else { - last = (scope[iterator + '_page'] + 4 < scope[iterator + '_num_pages']) ? - scope[iterator + '_page'] + 4 : scope[iterator + '_num_pages']; - } - for (i = first; i <= last; i++) { - scope[iterator + '_page_range'].push(i); - } - }; - } -]) - -.factory('RelatedPaginateInit', ['RefreshRelated', '$cookieStore', 'Wait', - function (RefreshRelated, $cookieStore, Wait) { - return function (params) { - - var scope = params.scope, - relatedSets = params.relatedSets, - pageSize = (params.pageSize) ? params.pageSize : 10, - key; - - for (key in relatedSets) { - scope[relatedSets[key].iterator + '_url'] = relatedSets[key].url; - scope[relatedSets[key].iterator + '_page'] = 0; - scope[relatedSets[key].iterator + '_page_size'] = pageSize; - } - - scope.getPage = function (page, set, iterator) { - var new_url = scope[iterator + '_url'].replace(/.page\=\d+/, ''), - connect = (/\/$/.test(new_url)) ? '?' : '&'; - new_url += connect + 'page=' + page; - new_url += (scope[iterator + 'SearchParams']) ? '&' + scope[iterator + 'SearchParams'] + - '&page_size=' + scope[iterator + '_page_size'] : 'page_size=' + scope[iterator + 'PageSize']; - Wait('start'); - RefreshRelated({ scope: scope, set: set, iterator: iterator, url: new_url }); - }; - - scope.pageIsActive = function (page, iterator) { - return (page === scope[iterator + '_page']) ? 'active' : ''; - }; - - scope.changePageSize = function (set, iterator) { - // Called when a new page size is selected + var scope = params.scope, + count = params.count, + next = params.next, + previous = params.previous, + iterator = params.iterator, + i, first, last; scope[iterator + '_page'] = 1; - var url = scope[iterator + '_url']; + scope[iterator + '_num_pages'] = Math.ceil((count / scope[iterator + '_page_size'])); + scope[iterator + '_num_pages'] = (scope[iterator + '_num_pages'] <= 0) ? 1 : scope[iterator + '_num_pages']; + scope[iterator + '_total_rows'] = count; - // Using the session cookie, keep track of user rows per page selection - $cookieStore.put(iterator + '_page_size', scope[iterator + '_page_size']); - - url = url.replace(/\/\?.*$/, '/'); - url += (scope[iterator + 'SearchParams']) ? '?' + scope[iterator + 'SearchParams'] + '&page_size=' + scope[iterator + '_page_size'] : - '?page_size=' + scope[iterator + '_page_size']; - - RefreshRelated({ - scope: scope, - set: set, - iterator: iterator, - url: url - }); - }; - }; - } -]) - - -.factory('PaginateInit', ['Refresh', '$cookieStore', 'Wait', - function (Refresh, $cookieStore, Wait) { - return function (params) { - - var scope = params.scope, - list = params.list, - iterator = (params.iterator) ? params.iterator : list.iterator, - pageSize = params.pageSize, - mode = (params.mode) ? params.mode : null; - - scope[iterator + '_page'] = (params.page) ? params.page : 1; - scope[iterator + '_url'] = params.url; - scope[iterator + '_mode'] = mode; - - if (pageSize) { - scope[iterator + '_page_size'] = params.pageSize; - } else if (mode === 'lookup') { - scope[iterator + '_page_size'] = 5; - } else { - scope[iterator + '_page_size'] = 20; - } - - scope.getPage = function (page, set, iterator) { - var new_url = scope[iterator + '_url'].replace(/.page\=\d+/, ''), - connect = (/\/$/.test(new_url)) ? '?' : '&'; - new_url += connect + 'page=' + page; - new_url += (scope[iterator + 'SearchParams']) ? '&' + scope[iterator + 'SearchParams'] + - '&page_size=' + scope[iterator + '_page_size'] : '&page_size=' + scope[iterator + 'PageSize']; - Wait('start'); - Refresh({ scope: scope, set: set, iterator: iterator, url: new_url }); - }; - - scope.pageIsActive = function (page, iterator) { - return (page === scope[iterator + '_page']) ? 'active' : ''; - }; - - scope.changePageSize = function (set, iterator, spinner) { - // Called whenever a new page size is selected - scope[iterator + '_page'] = 1; - var new_url = scope[iterator + '_url'].replace(/\?page_size\=\d+/, ''), - connect = (/\/$/.test(new_url)) ? '?' : '&'; - new_url += (scope[iterator + 'SearchParams']) ? connect + scope[iterator + 'SearchParams'] + '&page_size=' + scope[iterator + '_page_size'] : - connect + 'page_size=' + scope[iterator + '_page_size']; - if (spinner === undefined || spinner === true) { - Wait('start'); + // Which page are we on? + if (Empty(next) && previous) { + // no next page, but there is a previous page + scope[iterator + '_page'] = parseInt(previous.match(/page=\d+/)[0].replace(/page=/, '')) + 1; + } else if (next && Empty(previous)) { + // next page available, but no previous page + scope[iterator + '_page'] = 1; + } else if (next && previous) { + // we're in between next and previous + scope[iterator + '_page'] = parseInt(previous.match(/page=\d+/)[0].replace(/page=/, '')) + 1; + } + + // Calc the range of up to 10 pages to show + scope[iterator + '_page_range'] = []; + first = (scope[iterator + '_page'] > 5) ? scope[iterator + '_page'] - 5 : 1; + if (scope[iterator + '_page'] < 6) { + last = (10 <= scope[iterator + '_num_pages']) ? 10 : scope[iterator + '_num_pages']; + } else { + last = (scope[iterator + '_page'] + 4 < scope[iterator + '_num_pages']) ? + scope[iterator + '_page'] + 4 : scope[iterator + '_num_pages']; + } + for (i = first; i <= last; i++) { + scope[iterator + '_page_range'].push(i); } - Refresh({ scope: scope, set: set, iterator: iterator, url: new_url }); }; - }; - } -]); + } + ]) + + .factory('RelatedPaginateInit', ['RefreshRelated', '$cookieStore', 'Wait', + function (RefreshRelated, $cookieStore, Wait) { + return function (params) { + + var scope = params.scope, + relatedSets = params.relatedSets, + pageSize = (params.pageSize) ? params.pageSize : 10, + key; + + for (key in relatedSets) { + scope[relatedSets[key].iterator + '_url'] = relatedSets[key].url; + scope[relatedSets[key].iterator + '_page'] = 0; + scope[relatedSets[key].iterator + '_page_size'] = pageSize; + } + + scope.getPage = function (page, set, iterator) { + var new_url = scope[iterator + '_url'].replace(/.page\=\d+/, ''), + connect = (/\/$/.test(new_url)) ? '?' : '&'; + new_url += connect + 'page=' + page; + new_url += (scope[iterator + 'SearchParams']) ? '&' + scope[iterator + 'SearchParams'] + + '&page_size=' + scope[iterator + '_page_size'] : 'page_size=' + scope[iterator + 'PageSize']; + Wait('start'); + RefreshRelated({ scope: scope, set: set, iterator: iterator, url: new_url }); + }; + + scope.pageIsActive = function (page, iterator) { + return (page === scope[iterator + '_page']) ? 'active' : ''; + }; + + scope.changePageSize = function (set, iterator) { + // Called when a new page size is selected + + scope[iterator + '_page'] = 1; + var url = scope[iterator + '_url']; + + // Using the session cookie, keep track of user rows per page selection + $cookieStore.put(iterator + '_page_size', scope[iterator + '_page_size']); + + url = url.replace(/\/\?.*$/, '/'); + url += (scope[iterator + 'SearchParams']) ? '?' + scope[iterator + 'SearchParams'] + '&page_size=' + scope[iterator + '_page_size'] : + '?page_size=' + scope[iterator + '_page_size']; + + RefreshRelated({ + scope: scope, + set: set, + iterator: iterator, + url: url + }); + }; + }; + } + ]) + + + .factory('PaginateInit', ['Refresh', '$cookieStore', 'Wait', + function (Refresh, $cookieStore, Wait) { + return function (params) { + + var scope = params.scope, + list = params.list, + iterator = (params.iterator) ? params.iterator : list.iterator, + pageSize = params.pageSize, + mode = (params.mode) ? params.mode : null; + + scope[iterator + '_page'] = (params.page) ? params.page : 1; + scope[iterator + '_url'] = params.url; + scope[iterator + '_mode'] = mode; + + if (pageSize) { + scope[iterator + '_page_size'] = params.pageSize; + } else if (mode === 'lookup') { + scope[iterator + '_page_size'] = 5; + } else { + scope[iterator + '_page_size'] = 20; + } + + scope.getPage = function (page, set, iterator) { + var new_url = scope[iterator + '_url'].replace(/.page\=\d+/, ''), + connect = (/\/$/.test(new_url)) ? '?' : '&'; + new_url += connect + 'page=' + page; + new_url += (scope[iterator + 'SearchParams']) ? '&' + scope[iterator + 'SearchParams'] + + '&page_size=' + scope[iterator + '_page_size'] : '&page_size=' + scope[iterator + 'PageSize']; + Wait('start'); + Refresh({ scope: scope, set: set, iterator: iterator, url: new_url }); + }; + + scope.pageIsActive = function (page, iterator) { + return (page === scope[iterator + '_page']) ? 'active' : ''; + }; + + scope.changePageSize = function (set, iterator, spinner) { + // Called whenever a new page size is selected + scope[iterator + '_page'] = 1; + var new_url = scope[iterator + '_url'].replace(/\?page_size\=\d+/, ''), + connect = (/\/$/.test(new_url)) ? '?' : '&'; + new_url += (scope[iterator + 'SearchParams']) ? connect + scope[iterator + 'SearchParams'] + '&page_size=' + scope[iterator + '_page_size'] : + connect + 'page_size=' + scope[iterator + '_page_size']; + if (spinner === undefined || spinner === true) { + Wait('start'); + } + Refresh({ scope: scope, set: set, iterator: iterator, url: new_url }); + }; + }; + } + ]); diff --git a/awx/ui/static/js/helpers/Parse.js b/awx/ui/static/js/helpers/Parse.js index b459a72311..fe4669492e 100644 --- a/awx/ui/static/js/helpers/Parse.js +++ b/awx/ui/static/js/helpers/Parse.js @@ -13,86 +13,86 @@ */ +export default + angular.module('ParseHelper', ['Utilities', 'AngularCodeMirrorModule']) + .factory('ParseTypeChange', ['Alert', 'AngularCodeMirror', function (Alert, AngularCodeMirror) { + return function (params) { -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; - 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; + function removeField() { + //set our model to the last change in CodeMirror and then destroy CodeMirror + scope[fld] = scope.codeMirror.getValue(); - function removeField() { - //set our model to the last change in CodeMirror and then destroy CodeMirror - scope[fld] = scope.codeMirror.getValue(); - - // codeMirror.destroy looks for anything with a CodeMirror class and destroys it, so if there are multiple codeMirror editor instances, it will delete them all, - // // which was the case if launching a job from the job template form. I had to add a check to see if there were multiple instances and only remove the second one found on the modal. - // if( $(".CodeMirror").length >1) { - // var self = scope.codeMirror; - // $('.CodeMirror:eq(1)').empty().remove(); - // if (self.element) { - // self.element.show(); - // } - // } - // else - scope.codeMirror.destroy(); - } - - function createField(onChange, onReady) { - //hide the textarea and show a fresh CodeMirror with the current mode (json or yaml) - scope.codeMirror = AngularCodeMirror(); - scope.codeMirror.addModes($AnsibleConfig.variable_edit_modes); - scope.codeMirror.showTextArea({ scope: scope, model: fld, element: field_id, mode: scope[pfld], onReady: onReady, onChange: onChange }); - } - - // Hide the textarea and show a CodeMirror editor - createField(onChange, onReady); - - - // Toggle displayed variable string between JSON and YAML - scope.parseTypeChange = function() { - var json_obj; - if (scope[pfld] === 'json') { - // converting yaml to json - try { - removeField(); - json_obj = jsyaml.load(scope[fld]); - if ($.isEmptyObject(json_obj)) { - scope[fld] = "{}"; - } - else { - scope[fld] = JSON.stringify(json_obj, null, " "); - } - createField(); - } - catch (e) { - Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message); - setTimeout( function() { scope.$apply( function() { scope[pfld] = 'yaml'; createField(); }); }, 500); - } + // codeMirror.destroy looks for anything with a CodeMirror class and destroys it, so if there are multiple codeMirror editor instances, it will delete them all, + // // which was the case if launching a job from the job template form. I had to add a check to see if there were multiple instances and only remove the second one found on the modal. + // if( $(".CodeMirror").length >1) { + // var self = scope.codeMirror; + // $('.CodeMirror:eq(1)').empty().remove(); + // if (self.element) { + // self.element.show(); + // } + // } + // else + scope.codeMirror.destroy(); } - else { - // convert json to yaml - try { - removeField(); - json_obj = JSON.parse(scope[fld]); - if ($.isEmptyObject(json_obj)) { - scope[fld] = '---'; - } - else { - scope[fld] = jsyaml.safeDump(json_obj); - } - createField(); - } - catch (e) { - Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message); - setTimeout( function() { scope.$apply( function() { scope[pfld] = 'json'; createField(); }); }, 500 ); - } + + function createField(onChange, onReady) { + //hide the textarea and show a fresh CodeMirror with the current mode (json or yaml) + scope.codeMirror = AngularCodeMirror(); + scope.codeMirror.addModes($AnsibleConfig.variable_edit_modes); + scope.codeMirror.showTextArea({ scope: scope, model: fld, element: field_id, mode: scope[pfld], onReady: onReady, onChange: onChange }); } + + // Hide the textarea and show a CodeMirror editor + createField(onChange, onReady); + + + // Toggle displayed variable string between JSON and YAML + scope.parseTypeChange = function() { + var json_obj; + if (scope[pfld] === 'json') { + // converting yaml to json + try { + removeField(); + json_obj = jsyaml.load(scope[fld]); + if ($.isEmptyObject(json_obj)) { + scope[fld] = "{}"; + } + else { + scope[fld] = JSON.stringify(json_obj, null, " "); + } + createField(); + } + catch (e) { + Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message); + setTimeout( function() { scope.$apply( function() { scope[pfld] = 'yaml'; createField(); }); }, 500); + } + } + else { + // convert json to yaml + try { + removeField(); + json_obj = JSON.parse(scope[fld]); + if ($.isEmptyObject(json_obj)) { + scope[fld] = '---'; + } + else { + scope[fld] = jsyaml.safeDump(json_obj); + } + createField(); + } + catch (e) { + Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message); + setTimeout( function() { scope.$apply( function() { scope[pfld] = 'json'; createField(); }); }, 500 ); + } + } + }; }; - }; - } -]); \ No newline at end of file + } + ]); diff --git a/awx/ui/static/js/helpers/Permissions.js b/awx/ui/static/js/helpers/Permissions.js index de2a64156b..dfe4be2867 100644 --- a/awx/ui/static/js/helpers/Permissions.js +++ b/awx/ui/static/js/helpers/Permissions.js @@ -10,45 +10,47 @@ * Functions shared amongst Permission related controllers * */ -angular.module('PermissionsHelper', []) -// Handle category change event -.factory('PermissionCategoryChange', ['$sce', - function ($sce) { - return function (params) { - var scope = params.scope, - reset = params.reset, - html; +export default + angular.module('PermissionsHelper', []) - if (scope.category === 'Inventory') { - scope.projectrequired = false; - html = "
    \n" + - "
    Read
    \n" + - "
    Only allow the user or team to view the inventory.
    \n" + - "
    Write
    \n" + - "
    Allow the user or team to modify hosts and groups contained in the inventory, add new hosts and groups, and perform inventory sync operations.\n" + - "
    Admin
    \n" + - "
    Allow the user or team full access to the inventory. This includes reading, writing, deletion of the inventory and inventory sync operations.
    \n" + - "
    \n"; - scope.permissionTypeHelp = $sce.trustAsHtml(html); - } else { - scope.projectrequired = true; - html = "
    \n" + - "
    Create
    \n" + - "
    Allow the user or team to create job templates. This implies that they have the Run and Check permissions.
    \n" + - "
    Run
    \n" + - "
    Allow the user or team to run a job template from the project against the inventory. In Run mode modules will " + - "be executed, and changes to the inventory will occur.
    \n" + - "
    Check
    \n" + - "
    Only allow the user or team to run the project against the inventory as a dry-run operation. In Check mode, module operations " + - "will only be simulated. No changes will occur.
    \n" + - "
    \n"; - scope.permissionTypeHelp = $sce.trustAsHtml(html); - } + // Handle category change event + .factory('PermissionCategoryChange', ['$sce', + function ($sce) { + return function (params) { + var scope = params.scope, + reset = params.reset, + html; - if (reset) { - scope.permission_type = (scope.category === 'Inventory') ? 'read' : 'run'; //default to the first option - } - }; - } -]); + if (scope.category === 'Inventory') { + scope.projectrequired = false; + html = "
    \n" + + "
    Read
    \n" + + "
    Only allow the user or team to view the inventory.
    \n" + + "
    Write
    \n" + + "
    Allow the user or team to modify hosts and groups contained in the inventory, add new hosts and groups, and perform inventory sync operations.\n" + + "
    Admin
    \n" + + "
    Allow the user or team full access to the inventory. This includes reading, writing, deletion of the inventory and inventory sync operations.
    \n" + + "
    \n"; + scope.permissionTypeHelp = $sce.trustAsHtml(html); + } else { + scope.projectrequired = true; + html = "
    \n" + + "
    Create
    \n" + + "
    Allow the user or team to create job templates. This implies that they have the Run and Check permissions.
    \n" + + "
    Run
    \n" + + "
    Allow the user or team to run a job template from the project against the inventory. In Run mode modules will " + + "be executed, and changes to the inventory will occur.
    \n" + + "
    Check
    \n" + + "
    Only allow the user or team to run the project against the inventory as a dry-run operation. In Check mode, module operations " + + "will only be simulated. No changes will occur.
    \n" + + "
    \n"; + scope.permissionTypeHelp = $sce.trustAsHtml(html); + } + + if (reset) { + scope.permission_type = (scope.category === 'Inventory') ? 'read' : 'run'; //default to the first option + } + }; + } + ]); diff --git a/awx/ui/static/js/helpers/ProjectPath.js b/awx/ui/static/js/helpers/ProjectPath.js index 4a71485960..66d7eac683 100644 --- a/awx/ui/static/js/helpers/ProjectPath.js +++ b/awx/ui/static/js/helpers/ProjectPath.js @@ -12,74 +12,76 @@ * scope.base_dir (readonly field). * */ -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; +export default + angular.module('ProjectPathHelper', ['RestServices', 'Utilities']) + .factory('GetProjectPath', ['Alert', 'Rest', 'GetBasePath', 'ProcessErrors', + function (Alert, Rest, GetBasePath, ProcessErrors) { + return function (params) { - 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++) { + 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++) { - if (data[i].value === names[j]) { - newData.push(data[i]); - } + names.push(data[i].value); } - } - return newData; - } - - scope.showMissingPlaybooksAlert = false; - - Rest.setUrl(GetBasePath('config')); - Rest.get() - .success(function (data) { - var opts = [], i; - 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; + 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]); } } } - 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; - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to access API config. GET status: ' + status }); - }); - }; - } - ]); \ No newline at end of file + return newData; + } + + scope.showMissingPlaybooksAlert = false; + + Rest.setUrl(GetBasePath('config')); + Rest.get() + .success(function (data) { + var opts = [], i; + 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; + } + }) + .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/static/js/helpers/Projects.js b/awx/ui/static/js/helpers/Projects.js index 796c807476..519af33e98 100644 --- a/awx/ui/static/js/helpers/Projects.js +++ b/awx/ui/static/js/helpers/Projects.js @@ -14,63 +14,63 @@ */ +export default + angular.module('ProjectsHelper', ['RestServices', 'Utilities', 'ProjectStatusDefinition', 'ProjectFormDefinition']) -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': + result = 'error'; + } + return result; + }; + }]) - .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': - result = 'error'; - } - return result; - }; - }]) - - .factory('GetProjectToolTip', [ function() { - return function(status) { - var result = ''; - switch (status) { - case 'n/a': - case 'ok': - case 'never updated': - result = 'No SCM updates have run for this project'; - break; - case 'pending': - case 'waiting': - case 'new': - result = 'Queued. Click for details'; - break; - case 'updating': - case 'running': - result = 'Running! Click for details'; - break; - case 'successful': - result = 'Success! Click for details'; - break; - case 'failed': - case 'missing': - result = 'Failed. Click for details'; - } - return result; - }; - }]); \ No newline at end of file + .factory('GetProjectToolTip', [ function() { + return function(status) { + var result = ''; + switch (status) { + case 'n/a': + case 'ok': + case 'never updated': + result = 'No SCM updates have run for this project'; + break; + case 'pending': + case 'waiting': + case 'new': + result = 'Queued. Click for details'; + break; + case 'updating': + case 'running': + result = 'Running! Click for details'; + break; + case 'successful': + result = 'Success! Click for details'; + break; + case 'failed': + case 'missing': + result = 'Failed. Click for details'; + } + return result; + }; + }]); diff --git a/awx/ui/static/js/helpers/Schedules.js b/awx/ui/static/js/helpers/Schedules.js index aa0ef626dc..40325df133 100644 --- a/awx/ui/static/js/helpers/Schedules.js +++ b/awx/ui/static/js/helpers/Schedules.js @@ -12,635 +12,635 @@ */ +export default + angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ModalDialog', + 'GeneratorHelpers']) -angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ModalDialog', - 'GeneratorHelpers']) + .factory('ShowSchedulerModal', ['Wait', 'CreateDialog', function(Wait, CreateDialog) { + return function(params) { + // Set modal dimensions based on viewport width - .factory('ShowSchedulerModal', ['Wait', 'CreateDialog', function(Wait, CreateDialog) { - return function(params) { - // Set modal dimensions based on viewport width + var buttons, + scope = params.scope, + callback = params.callback; - var buttons, - scope = params.scope, - callback = params.callback; - - buttons = [{ - "label": "Cancel", - "onClick": function() { - $(this).dialog('close'); - }, - "icon": "fa-times", - "class": "btn btn-default", - "id": "schedule-close-button" - },{ - "label": "Save", - "onClick": function() { - setTimeout(function(){ - scope.$apply(function(){ - scope.saveSchedule(); + buttons = [{ + "label": "Cancel", + "onClick": function() { + $(this).dialog('close'); + }, + "icon": "fa-times", + "class": "btn btn-default", + "id": "schedule-close-button" + },{ + "label": "Save", + "onClick": function() { + setTimeout(function(){ + scope.$apply(function(){ + scope.saveSchedule(); + }); }); - }); - }, - "icon": "fa-check", - "class": "btn btn-primary", - "id": "schedule-save-button" - }]; + }, + "icon": "fa-check", + "class": "btn btn-primary", + "id": "schedule-save-button" + }]; - CreateDialog({ - id: 'scheduler-modal-dialog', - scope: scope, - buttons: buttons, - width: 700, - height: 725, - minWidth: 400, - onClose: function() { - $('#scheduler-modal-dialog #form-container').empty(); - }, - onOpen: function() { - Wait('stop'); - $('#scheduler-tabs a:first').tab('show'); + CreateDialog({ + id: 'scheduler-modal-dialog', + scope: scope, + buttons: buttons, + width: 700, + height: 725, + minWidth: 400, + onClose: function() { + $('#scheduler-modal-dialog #form-container').empty(); + }, + onOpen: function() { + Wait('stop'); + $('#scheduler-tabs a:first').tab('show'); + $('#schedulerName').focus(); + $('#rrule_nlp_description').dblclick(function() { + setTimeout(function() { scope.$apply(function() { scope.showRRule = (scope.showRRule) ? false : true; }); }, 100); + }); + }, + callback: callback + }); + }; + }]) + + .factory('EditSchedule', ['SchedulerInit', 'ShowSchedulerModal', 'Wait', 'Rest', 'ProcessErrors', 'GetBasePath', 'SchedulePost', + function(SchedulerInit, ShowSchedulerModal, Wait, Rest, ProcessErrors, GetBasePath, SchedulePost) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback, + schedule, scheduler, + url = GetBasePath('schedules') + id + '/'; + + if (scope.removeDialogReady) { + scope.removeDialogReady(); + } + scope.removeDialogReady = scope.$on('DialogReady', function() { + $('#scheduler-modal-dialog').dialog('open'); $('#schedulerName').focus(); - $('#rrule_nlp_description').dblclick(function() { - setTimeout(function() { scope.$apply(function() { scope.showRRule = (scope.showRRule) ? false : true; }); }, 100); + setTimeout(function() { + scope.$apply(function() { + scheduler.setRRule(schedule.rrule); + scheduler.setName(schedule.name); + }); + }, 300); + }); + + 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='); + + ShowSchedulerModal({ scope: scope, callback: 'DialogReady' }); + scope.showRRuleDetail = false; + }); + + + if (scope.removeScheduleSaved) { + scope.removeScheduleSaved(); + } + scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { + Wait('stop'); + $('#scheduler-modal-dialog').dialog('close'); + if (callback) { + scope.$emit(callback); + } + }); + + scope.saveSchedule = function() { + $('#scheduler-tabs a:first').tab('show'); + SchedulePost({ + scope: scope, + url: url, + scheduler: scheduler, + callback: 'ScheduleSaved', + mode: 'edit', + schedule: schedule }); - }, - callback: callback - }); - }; - }]) + }; - .factory('EditSchedule', ['SchedulerInit', 'ShowSchedulerModal', 'Wait', 'Rest', 'ProcessErrors', 'GetBasePath', 'SchedulePost', - function(SchedulerInit, ShowSchedulerModal, Wait, Rest, ProcessErrors, GetBasePath, SchedulePost) { - return function(params) { - var scope = params.scope, - id = params.id, - callback = params.callback, - schedule, scheduler, - url = GetBasePath('schedules') + id + '/'; + $('#scheduler-tabs li a').on('shown.bs.tab', function(e) { + if ($(e.target).text() === 'Details') { + if (!scheduler.isValid()) { + $('#scheduler-tabs a:first').tab('show'); + } + } + }); - if (scope.removeDialogReady) { - scope.removeDialogReady(); - } - scope.removeDialogReady = scope.$on('DialogReady', function() { - $('#scheduler-modal-dialog').dialog('open'); - $('#schedulerName').focus(); - setTimeout(function() { - scope.$apply(function() { - scheduler.setRRule(schedule.rrule); - scheduler.setName(schedule.name); + Wait('start'); + + // Get the existing record + Rest.setUrl(url); + Rest.get() + .success(function(data) { + schedule = data; + scope.$emit('ScheduleFound'); + }) + .error(function(data,status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); }); - }, 300); - }); + }; + }]) - if (scope.removeScheduleFound) { - scope.removeScheduleFound(); - } - scope.removeScheduleFound = scope.$on('ScheduleFound', function() { + .factory('AddSchedule', ['$location', '$routeParams', 'SchedulerInit', 'ShowSchedulerModal', 'Wait', 'GetBasePath', 'Empty', + 'SchedulePost', + function($location, $routeParams, SchedulerInit, ShowSchedulerModal, Wait, GetBasePath, Empty, SchedulePost) { + return function(params) { + var scope = params.scope, + callback= params.callback, + base = $location.path().replace(/^\//, '').split('/')[0], + url = GetBasePath(base), + scheduler; + + if (!Empty($routeParams.template_id)) { + url += $routeParams.template_id + '/schedules/'; + } + else if (!Empty($routeParams.id)) { + url += $routeParams.id + '/schedules/'; + } + + if (scope.removeDialogReady) { + scope.removeDialogReady(); + } + scope.removeDialogReady = scope.$on('DialogReady', function() { + $('#scheduler-modal-dialog').dialog('open'); + $('#schedulerName').focus(); + }); + + Wait('start'); $('#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='); - + scheduler.clear(); ShowSchedulerModal({ scope: scope, callback: 'DialogReady' }); scope.showRRuleDetail = false; - }); - - if (scope.removeScheduleSaved) { - scope.removeScheduleSaved(); - } - scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { - Wait('stop'); - $('#scheduler-modal-dialog').dialog('close'); - if (callback) { - scope.$emit(callback); + if (scope.removeScheduleSaved) { + scope.removeScheduleSaved(); } - }); + scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { + Wait('stop'); + $('#scheduler-modal-dialog').dialog('close'); + if (callback) { + scope.$emit(callback); + } + }); - scope.saveSchedule = function() { - $('#scheduler-tabs a:first').tab('show'); - SchedulePost({ - scope: scope, - url: url, - scheduler: scheduler, - callback: 'ScheduleSaved', - mode: 'edit', - schedule: schedule + scope.saveSchedule = function() { + $('#scheduler-tabs a:first').tab('show'); + 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'); + } + } }); }; + }]) - $('#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; + + 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(); + 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 }); + }); } - } - }); - - Wait('start'); - - // Get the existing record - Rest.setUrl(url); - Rest.get() - .success(function(data) { - schedule = 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', '$routeParams', 'SchedulerInit', 'ShowSchedulerModal', 'Wait', 'GetBasePath', 'Empty', - 'SchedulePost', - function($location, $routeParams, SchedulerInit, ShowSchedulerModal, Wait, GetBasePath, Empty, SchedulePost) { - return function(params) { - var scope = params.scope, - callback= params.callback, - base = $location.path().replace(/^\//, '').split('/')[0], - url = GetBasePath(base), - scheduler; - - if (!Empty($routeParams.template_id)) { - url += $routeParams.template_id + '/schedules/'; - } - else if (!Empty($routeParams.id)) { - url += $routeParams.id + '/schedules/'; - } - - if (scope.removeDialogReady) { - scope.removeDialogReady(); - } - scope.removeDialogReady = scope.$on('DialogReady', function() { - $('#scheduler-modal-dialog').dialog('open'); - $('#schedulerName').focus(); - }); - - Wait('start'); - $('#form-container').empty(); - scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false }); - scheduler.inject('form-container', false); - scheduler.injectDetail('occurrences', false); - scheduler.clear(); - ShowSchedulerModal({ scope: scope, callback: 'DialogReady' }); - scope.showRRuleDetail = false; - - if (scope.removeScheduleSaved) { - scope.removeScheduleSaved(); - } - scope.removeScheduleSaved = scope.$on('ScheduleSaved', function() { - Wait('stop'); - $('#scheduler-modal-dialog').dialog('close'); - if (callback) { - scope.$emit(callback); - } - }); - - scope.saveSchedule = function() { - $('#scheduler-tabs a:first').tab('show'); - 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'); + else { + Rest.put(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 }); + }); } } - }); - }; - }]) - - .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; - - 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(); - 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); - } - else { - Wait('stop'); - } - }) - .error(function(data, status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'POST to ' + url + ' returned: ' + status }); - }); + return false; } - } - else { - return false; - } - }; - }]) + }; + }]) - /** - * Inject the scheduler_dialog.html wherever needed - */ - .factory('LoadDialogPartial', ['Rest', '$compile', 'ProcessErrors', function(Rest, $compile, ProcessErrors) { - return function(params) { + /** + * Inject the scheduler_dialog.html wherever needed + */ + .factory('LoadDialogPartial', ['Rest', '$compile', 'ProcessErrors', function(Rest, $compile, ProcessErrors) { + return function(params) { - var scope = params.scope, - element_id = params.element_id, - callback = params.callback, - url; + var scope = params.scope, + element_id = params.element_id, + callback = params.callback, + url; - // Add the schedule_dialog.html partial - url = '/static/partials/schedule_dialog.html'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - var e = angular.element(document.getElementById(element_id)); - e.append(data); - $compile(e)(scope); - scope.$emit(callback); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status }); - }); - }; - }]) - - /** - * 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', function(Wait, GetBasePath, ProcessErrors, Rest) { - return function(params) { - var scope = params.scope, - id = params.id, - callback = params.callback, - 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() { - if (callback) { - scope.$emit(callback, id); - } - else { - Wait('stop'); - } + // Add the schedule_dialog.html partial + url = '/static/partials/schedule_dialog.html'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + var e = angular.element(document.getElementById(element_id)); + e.append(data); + $compile(e)(scope); + scope.$emit(callback); }) - .error( function() { + .error(function(data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status }); + msg: 'Call to ' + url + ' failed. GET returned: ' + status }); }); - }); + }; + }]) - Wait('start'); + /** + * 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', function(Wait, GetBasePath, ProcessErrors, Rest) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback, + url = GetBasePath('schedules') + id +'/'; - // 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 }); + // 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() { + if (callback) { + scope.$emit(callback, id); + } + else { + Wait('stop'); + } + }) + .error( function() { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to update schedule ' + id + ' PUT 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', 'ProcessErrors', 'Prompt', 'Find', - function(GetBasePath, Rest, Wait, ProcessErrors, Prompt, Find) { - 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'); + + // Get the schedule Rest.setUrl(url); - Rest.destroy() - .success(function () { - $('#prompt-modal').modal('hide'); - scope.$emit(callback, id); + Rest.get() + .success(function(data) { + scope.$emit('ScheduleFound', data); }) - .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 }); + .error(function(data,status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); }); }; + }]) - Prompt({ - hdr: hdr, - body: "
    Are you sure you want to delete the " + schedule.name + " schedule?
    ", - action: action, - backdrop: false - }); + /** + * 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', 'ProcessErrors', 'Prompt', 'Find', + function(GetBasePath, Rest, Wait, ProcessErrors, Prompt, Find) { + return function(params) { - }; - }]) + var scope = params.scope, + id = params.id, + callback = params.callback, + action, schedule, list, url, hdr; - /** - * 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; - }; - }]) - - - .factory('SchedulesControllerInit', ['$location', 'ToggleSchedule', 'DeleteSchedule', 'EditSchedule', 'AddSchedule', - function($location, ToggleSchedule, DeleteSchedule, EditSchedule, AddSchedule) { - return function(params) { - var scope = params.scope, - parent_scope = params.parent_scope, - iterator = (params.iterator) ? params.iterator : scope.iterator, - base = $location.path().replace(/^\//, '').split('/')[0]; - - scope.toggleSchedule = function(event, id) { - try { - $(event.target).tooltip('hide'); + if (scope.schedules) { + list = scope.schedules; } - catch(e) { - // ignore + else if (scope.scheduled_jobs) { + list = scope.scheduled_jobs; } - ToggleSchedule({ - scope: scope, - id: id, - callback: 'SchedulesRefresh' - }); - }; - scope.deleteSchedule = function(id) { - DeleteSchedule({ - scope: scope, - id: id, - callback: 'SchedulesRefresh' - }); - }; + url = GetBasePath('schedules') + id + '/'; + schedule = Find({list: list, key: 'id', val: id }); + hdr = 'Delete Schedule'; - scope.editSchedule = function(id) { - EditSchedule({ - scope: scope, - id: id, - callback: 'SchedulesRefresh' - }); - }; + action = function () { + Wait('start'); + Rest.setUrl(url); + Rest.destroy() + .success(function () { + $('#prompt-modal').modal('hide'); + scope.$emit(callback, id); + }) + .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 }); + }); + }; - scope.addSchedule = function() { - AddSchedule({ - scope: scope, - callback: 'SchedulesRefresh' + Prompt({ + hdr: hdr, + body: "
    Are you sure you want to delete the " + schedule.name + " schedule?
    ", + action: action, + backdrop: false }); - }; - scope.refreshSchedules = function() { - if (base === 'jobs') { - parent_scope.refreshJobs(); + }; + }]) + + /** + * 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; + }; + }]) + + + .factory('SchedulesControllerInit', ['$location', 'ToggleSchedule', 'DeleteSchedule', 'EditSchedule', 'AddSchedule', + function($location, ToggleSchedule, DeleteSchedule, EditSchedule, AddSchedule) { + return function(params) { + var scope = params.scope, + parent_scope = params.parent_scope, + iterator = (params.iterator) ? params.iterator : scope.iterator, + base = $location.path().replace(/^\//, '').split('/')[0]; + + scope.toggleSchedule = function(event, id) { + try { + $(event.target).tooltip('hide'); + } + catch(e) { + // ignore + } + ToggleSchedule({ + scope: scope, + id: id, + callback: 'SchedulesRefresh' + }); + }; + + scope.deleteSchedule = function(id) { + DeleteSchedule({ + scope: scope, + id: id, + callback: 'SchedulesRefresh' + }); + }; + + scope.editSchedule = function(id) { + EditSchedule({ + scope: scope, + id: id, + callback: 'SchedulesRefresh' + }); + }; + + scope.addSchedule = function() { + AddSchedule({ + scope: scope, + callback: 'SchedulesRefresh' + }); + }; + + scope.refreshSchedules = function() { + if (base === 'jobs') { + parent_scope.refreshJobs(); + } + else { + scope.search(iterator); + } + }; + + if (scope.removeSchedulesRefresh) { + scope.removeSchedulesRefresh(); } - else { + scope.$on('SchedulesRefresh', function() { scope.search(iterator); - } + }); }; + }]) - if (scope.removeSchedulesRefresh) { - scope.removeSchedulesRefresh(); - } - scope.$on('SchedulesRefresh', function() { - scope.search(iterator); - }); - }; - }]) + .factory('SchedulesListInit', [ function() { + return function(params) { + var scope = params.scope, + list = params.list, + choices = params.choices; + scope[list.name].forEach(function(item, item_idx) { + var fld, + field, + itm = scope[list.name][item_idx], + job = item.summary_fields.unified_job_template; - .factory('SchedulesListInit', [ function() { - return function(params) { - var scope = params.scope, - list = params.list, - choices = params.choices; - scope[list.name].forEach(function(item, item_idx) { - var fld, - field, - itm = scope[list.name][item_idx], - job = item.summary_fields.unified_job_template; - - itm.enabled = (itm.enabled) ? true : false; - if (itm.enabled) { - itm.play_tip = 'Schedule is active. Click to stop.'; - itm.status = 'active'; - itm.status_tip = 'Schedule is active. Click to stop.'; - } - else { - itm.play_tip = 'Schedule is stopped. Click to activate.'; - itm.status = 'stopped'; - itm.status_tip = 'Schedule is stopped. Click to activate.'; - } - itm.nameTip = item.name; - // include the word schedule if the schedule name does not include the word schedule - if (item.name.indexOf("schedule") === -1 && item.name.indexOf("Schedule") === -1) { - itm.nameTip += " schedule"; - } - itm.nameTip += " for "; - if (job.name.indexOf("job") === -1 && job.name.indexOf("Job") === -1) { - itm.nameTip += "job "; - } - itm.nameTip += job.name; - itm.nameTip += ". Click to edit schedule."; - // 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]; + itm.enabled = (itm.enabled) ? true : false; + if (itm.enabled) { + itm.play_tip = 'Schedule is active. Click to stop.'; + itm.status = 'active'; + itm.status_tip = 'Schedule is active. Click to stop.'; + } + else { + itm.play_tip = 'Schedule is stopped. Click to activate.'; + itm.status = 'stopped'; + itm.status_tip = 'Schedule is stopped. Click to activate.'; + } + itm.nameTip = item.name; + // include the word schedule if the schedule name does not include the word schedule + if (item.name.indexOf("schedule") === -1 && item.name.indexOf("Schedule") === -1) { + itm.nameTip += " schedule"; + } + itm.nameTip += " for "; + if (job.name.indexOf("job") === -1 && job.name.indexOf("Job") === -1) { + itm.nameTip += "job "; + } + itm.nameTip += job.name; + itm.nameTip += ". Click to edit schedule."; + // 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]; + } } } - } - // Set the item type label - if (list.fields.type) { - choices.every(function(choice) { - if (choice.value === item.type) { - itm.type_label = choice.label; - return false; - } - return true; + // Set the item type label + if (list.fields.type) { + choices.every(function(choice) { + if (choice.value === item.type) { + itm.type_label = choice.label; + return false; + } + return true; + }); + } + }); + }; + }]) + + /** + * + * Called from a controller to setup the scope for a schedules list + * + */ + .factory('LoadSchedulesScope', ['$compile', '$location', '$routeParams','SearchInit', 'PaginateInit', 'GenerateList', 'SchedulesControllerInit', + 'SchedulesListInit', 'SearchWidget', + function($compile, $location, $routeParams, SearchInit, PaginateInit, GenerateList, SchedulesControllerInit, SchedulesListInit, SearchWidget) { + return function(params) { + var parent_scope = params.parent_scope, + scope = params.scope, + list = params.list, + id = params.id, + url = params.url, + pageSize = params.pageSize || 5, + spinner = (params.spinner === undefined) ? true : params.spinner, + base = $location.path().replace(/^\//, '').split('/')[0], + e, html; + + if (base === 'jobs') { + // on jobs page the search widget appears on the right + html = SearchWidget({ + iterator: list.iterator, + template: params.list, + includeSize: false + }); + e = angular.element(document.getElementById(id + '-search-container')).append(html); + $compile(e)(scope); + GenerateList.inject(list, { + mode: 'edit', + id: id, + breadCrumbs: false, + scope: scope, + showSearch: false + }); + } + else { + GenerateList.inject(list, { + mode: 'edit', + id: id, + breadCrumbs: false, + scope: scope, + searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12', + showSearch: true }); } - }); - }; - }]) - /** - * - * Called from a controller to setup the scope for a schedules list - * - */ - .factory('LoadSchedulesScope', ['$compile', '$location', '$routeParams','SearchInit', 'PaginateInit', 'GenerateList', 'SchedulesControllerInit', - 'SchedulesListInit', 'SearchWidget', - function($compile, $location, $routeParams, SearchInit, PaginateInit, GenerateList, SchedulesControllerInit, SchedulesListInit, SearchWidget) { - return function(params) { - var parent_scope = params.parent_scope, - scope = params.scope, - list = params.list, - id = params.id, - url = params.url, - pageSize = params.pageSize || 5, - spinner = (params.spinner === undefined) ? true : params.spinner, - base = $location.path().replace(/^\//, '').split('/')[0], - e, html; - - if (base === 'jobs') { - // on jobs page the search widget appears on the right - html = SearchWidget({ - iterator: list.iterator, - template: params.list, - includeSize: false - }); - e = angular.element(document.getElementById(id + '-search-container')).append(html); - $compile(e)(scope); - GenerateList.inject(list, { - mode: 'edit', - id: id, - breadCrumbs: false, + SearchInit({ scope: scope, - showSearch: false + set: list.name, + list: list, + url: url }); - } - else { - GenerateList.inject(list, { - mode: 'edit', - id: id, - breadCrumbs: false, - scope: scope, - searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12', - showSearch: true - }); - } - SearchInit({ - scope: scope, - set: list.name, - list: list, - url: url - }); - - PaginateInit({ - scope: scope, - list: list, - url: url, - pageSize: pageSize - }); - - scope.iterator = list.iterator; - - if (scope.removePostRefresh) { - scope.removePostRefresh(); - } - scope.$on('PostRefresh', function(){ - SchedulesControllerInit({ - scope: scope, - parent_scope: parent_scope, - list: list - }); - SchedulesListInit({ + PaginateInit({ scope: scope, list: list, - choices: parent_scope.type_choices + url: url, + pageSize: pageSize }); - parent_scope.$emit('listLoaded'); - }); - if ($routeParams.id__int) { - scope[list.iterator + 'SearchField'] = 'id'; - scope[list.iterator + 'SearchValue'] = $routeParams.id__int; - scope[list.iterator + 'SearchFieldLabel'] = 'ID'; - } + scope.iterator = list.iterator; - scope.search(list.iterator, null, null, null, null, spinner); - }; - }]); + if (scope.removePostRefresh) { + scope.removePostRefresh(); + } + scope.$on('PostRefresh', function(){ + SchedulesControllerInit({ + scope: scope, + parent_scope: parent_scope, + list: list + }); + SchedulesListInit({ + scope: scope, + list: list, + choices: parent_scope.type_choices + }); + parent_scope.$emit('listLoaded'); + }); + + if ($routeParams.id__int) { + scope[list.iterator + 'SearchField'] = 'id'; + scope[list.iterator + 'SearchValue'] = $routeParams.id__int; + scope[list.iterator + 'SearchFieldLabel'] = 'ID'; + } + + scope.search(list.iterator, null, null, null, null, spinner); + }; + }]); diff --git a/awx/ui/static/js/helpers/Selection.js b/awx/ui/static/js/helpers/Selection.js index a843d8850a..8506812807 100644 --- a/awx/ui/static/js/helpers/Selection.js +++ b/awx/ui/static/js/helpers/Selection.js @@ -15,163 +15,163 @@ */ +export default + angular.module('SelectionHelper', ['Utilities', 'RestServices']) -angular.module('SelectionHelper', ['Utilities', 'RestServices']) + .factory('SelectionInit', ['Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'Wait', + function (Rest, Alert, ProcessErrors, ReturnToCaller, Wait) { + return function (params) { -.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; - 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) { - if ((scope[list.name][i].checked === "0" && !ischeckbox) || (scope[list.name][i].checked === "0" && ischeckbox)) { - // select the row - scope[list.name][i].checked = '1'; - 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 { - // unselect the row - scope[list.name][i].checked = '0'; - 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; + if (params.selected !== undefined) { + selected = params.selected; } else { - scope.formModalActionDisabled = true; - scope.disableSelectBtn = true; + selected = []; //array of selected row IDs } - }; - - // Add the selections - scope.finishSelection = function () { - Rest.setUrl(target_url); - - var queue = [], j; scope.formModalActionDisabled = true; scope.disableSelectBtn = true; - Wait('start'); + // 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) { + if ((scope[list.name][i].checked === "0" && !ischeckbox) || (scope[list.name][i].checked === "0" && ischeckbox)) { + // select the row + scope[list.name][i].checked = '1'; + 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 { + // unselect the row + scope[list.name][i].checked = '0'; + scope[list.name][i].success_class = ''; - 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++; + // remove selected object from the array + for (j = 0; j < selected.length; j++) { + if (selected[j].id === id) { + selected.splice(j, 1); + break; + } + } } } - if (errors === 0) { - finished(); + } + 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 = ''; + } } } }); - - 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/static/js/helpers/SocketHelper.js b/awx/ui/static/js/helpers/SocketHelper.js index 810e253dd5..ec57a29b31 100644 --- a/awx/ui/static/js/helpers/SocketHelper.js +++ b/awx/ui/static/js/helpers/SocketHelper.js @@ -12,34 +12,34 @@ */ +export default + angular.module('SocketHelper', ['Utilities', 'FFSocketHelpDefinition', 'SafariSocketHelpDefinition' , 'ChromeSocketHelpDefinition']) -angular.module('SocketHelper', ['Utilities', 'FFSocketHelpDefinition', 'SafariSocketHelpDefinition' , 'ChromeSocketHelpDefinition']) - -.factory('ShowSocketHelp', ['$location', '$rootScope', 'FFSocketHelp', 'SafariSocketHelp', 'ChromeSocketHelp', 'HelpDialog', -function($location, $rootScope, FFSocketHelp, SafariSocketHelp, ChromeSocketHelp, HelpDialog) { - return function() { - var scope = $rootScope.$new(); - scope.socketPort = $AnsibleConfig.websocket_port; - scope.socketURL = 'https://' + $location.host() + ':' + scope.socketPort + '/'; - if ($rootScope.browser === "FF") { - scope.browserName = "Firefox"; - HelpDialog({ defn: FFSocketHelp, scope: scope }); - } - else if ($rootScope.browser === "SAFARI") { - scope.browserName = "Safari"; - HelpDialog({ defn: SafariSocketHelp, scope: scope }); - } - else { - if ($rootScope.browser === "MSIE") { - scope.browserName = "Internet Explorer"; + .factory('ShowSocketHelp', ['$location', '$rootScope', 'FFSocketHelp', 'SafariSocketHelp', 'ChromeSocketHelp', 'HelpDialog', + function($location, $rootScope, FFSocketHelp, SafariSocketHelp, ChromeSocketHelp, HelpDialog) { + return function() { + var scope = $rootScope.$new(); + scope.socketPort = $AnsibleConfig.websocket_port; + scope.socketURL = 'https://' + $location.host() + ':' + scope.socketPort + '/'; + if ($rootScope.browser === "FF") { + scope.browserName = "Firefox"; + HelpDialog({ defn: FFSocketHelp, scope: scope }); } - else if ($rootScope.browser === "CHROME") { - scope.browserName = "Chrome"; + else if ($rootScope.browser === "SAFARI") { + scope.browserName = "Safari"; + HelpDialog({ defn: SafariSocketHelp, scope: scope }); } - else if ($rootScope.browser === "OPERA") { - scope.browserName = "Opera"; + else { + if ($rootScope.browser === "MSIE") { + scope.browserName = "Internet Explorer"; + } + else if ($rootScope.browser === "CHROME") { + scope.browserName = "Chrome"; + } + else if ($rootScope.browser === "OPERA") { + scope.browserName = "Opera"; + } + HelpDialog({ defn: ChromeSocketHelp, scope: scope }); } - HelpDialog({ defn: ChromeSocketHelp, scope: scope }); - } - }; -}]); \ No newline at end of file + }; + }]); diff --git a/awx/ui/static/js/helpers/Survey.js b/awx/ui/static/js/helpers/Survey.js index e616b4cc3e..91ff60b988 100644 --- a/awx/ui/static/js/helpers/Survey.js +++ b/awx/ui/static/js/helpers/Survey.js @@ -11,8 +11,7 @@ * */ - - +export default angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'ModalDialog' , 'GeneratorHelpers']) @@ -877,4 +876,3 @@ angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', }; }]); - diff --git a/awx/ui/static/js/helpers/Users.js b/awx/ui/static/js/helpers/Users.js index 2d43403fea..2b1da18180 100644 --- a/awx/ui/static/js/helpers/Users.js +++ b/awx/ui/static/js/helpers/Users.js @@ -11,33 +11,33 @@ */ +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. -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 = { - variable: "orgrequired", - init: true + 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 = { + variable: "orgrequired", + init: true + }; + UserForm.fields.organization.readonly = false; + UserForm.fields.username.awRequiredWhen = { + variable: "not_ldap_user", + init: true + }; + UserForm.fields.username.readonly = false; + UserForm.fields.password.editRequired = false; + UserForm.fields.password.addRrequired = true; }; - UserForm.fields.organization.readonly = false; - UserForm.fields.username.awRequiredWhen = { - variable: "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/static/js/helpers/Variables.js b/awx/ui/static/js/helpers/Variables.js index 9a840f2a3a..472fecea22 100644 --- a/awx/ui/static/js/helpers/Variables.js +++ b/awx/ui/static/js/helpers/Variables.js @@ -10,8 +10,7 @@ * */ - - +export default angular.module('VariablesHelper', ['Utilities']) /** diff --git a/awx/ui/static/js/helpers/api-defaults.js b/awx/ui/static/js/helpers/api-defaults.js index a3ae1e3fab..67804054d9 100644 --- a/awx/ui/static/js/helpers/api-defaults.js +++ b/awx/ui/static/js/helpers/api-defaults.js @@ -11,85 +11,85 @@ * @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) { -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 - //Reload a related collection on pagination or search change + var result = {}, cnt = 0, url; - 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 lookup(key) { - var id, result = {}; - for (id in $rootScope.apiDefaults) { - if (id === key || id.iterator === key) { - result[id] = $rootScope.apiDefaults[id]; - break; + function wait() { + if ($.isEmptyObject(result) && cnt < 5) { + cnt++; + setTimeout(1000, wait()); + } else if (result.status === 'success') { + return lookup(key); } } - return result; - } - function wait() { - if ($.isEmptyObject(result) && cnt < 5) { - cnt++; - setTimeout(1000, wait()); - } else if (result.status === 'success') { + 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); } - } - - 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); - } - }; - } - ]); \ No newline at end of file + }; + } + ]); diff --git a/awx/ui/static/js/helpers/inventory.js b/awx/ui/static/js/helpers/inventory.js index 5a4794b4e0..67ca22c3e1 100644 --- a/awx/ui/static/js/helpers/inventory.js +++ b/awx/ui/static/js/helpers/inventory.js @@ -11,387 +11,387 @@ * (controllers/Inventories.js) */ - -angular.module('InventoryHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', 'ListGenerator', 'AuthService', - 'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'SearchHelper', 'VariablesHelper', -]) +export default + angular.module('InventoryHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', 'ListGenerator', 'AuthService', + 'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'SearchHelper', 'VariablesHelper', + ]) -.factory('GetGroupContainerHeight', [ function() { - return function() { + .factory('GetGroupContainerHeight', [ function() { + return function() { - /*console.log('window height: ' + $(window).height()); - console.log('main-menu: ' + $('.main-menu').outerHeight()); - console.log('main_tabs: ' + $('#main_tabs').outerHeight()); - console.log('breadcrumbs: ' + $('#breadcrumbs').outerHeight()); - console.log('footer: ' + $('.site-footer').outerHeight()); - console.log('group-breadcrumbs: ' + $('.group-breadcrumbs').outerHeight()); - console.log('searchwidget: ' + $('#groups-container #search-widget-container').outerHeight()); - console.log('group table head: ' + $('#groups_table thead').height()); - console.log('subtotal: ' + ( $(window).height() - $('.main-menu').outerHeight() - $('#main_tabs').outerHeight() - $('#breadcrumbs').outerHeight() - - $('.site-footer').outerHeight() - $('.group-breadcrumbs').outerHeight() - $('#groups-container #search-widget-container').outerHeight() - - $('#groups_table thead').height() )); - */ - var height = $(window).height() - $('#main-menu-container .navbar').outerHeight() - $('#breadcrumbs').outerHeight() - - $('.site-footer').outerHeight() - $('.group-breadcrumbs').outerHeight() - $('#groups-container #search-widget-container').outerHeight() - - $('#groups_table thead').height() - 90; - height = (height < 600) ? 600 : height; - return height; - }; -}]) - -.factory('GetHostContainerRows', [ function() { - return function() { - var height, rows; - height = $('#hosts-container .list-well').height() - $('#hosts-constainer .list-well .row').height() - $('#hosts_table thead').height(); - rows = Math.floor(height / 30) - 2; - rows = (rows < 20) ? 20 : rows; - return rows; - }; -}]) - -.factory('GetGroupContainerRows', [ function() { - return function() { - var height, rows; - height = $('#groups-container .list-table-container').height(); - rows = Math.floor(height / 31) - 2; - rows = (rows < 20) ? 20 : rows; - return rows; - }; -}]) - -.factory('SetContainerHeights', [ 'GetGroupContainerHeight', 'GetHostContainerRows', function(GetGroupContainerHeight, GetHostContainerRows) { - return function(params) { - var group_scope = params.group_scope, - host_scope = params.host_scope, - reloadHosts = (params.reloadHosts) ? true : false, - height, rows; - - $('#groups-container .list-table-container').height('auto'); - $('#hosts-container .list-well').height('auto'); - $('#hosts-container .list-table-container').height('auto'); - - setTimeout(function() { - if ($(window).width() > 1210) { - height = GetGroupContainerHeight(); - $('#groups-container .list-table-container').height(height); - $('#hosts-container .list-table-container').height(height); - setTimeout(function() { $('#hosts-container .list-well').height( $('#groups-container .list-well').outerHeight()); }, 500); - } - - if (reloadHosts) { - // we need ro recalc the the page size - if ($(window).width() > 1210) { - rows = GetHostContainerRows(); - host_scope.host_page_size = rows; - group_scope.group_page_size = rows; - } - else { - // on small screens we go back to the default - host_scope.host_page_size = 20; - group_scope.group_page_size = 20; - } - host_scope.changePageSize('hosts', 'host'); - group_scope.changePageSize('groups', 'group'); - } - }, 100); - - }; -}]) - - -.factory('WatchInventoryWindowResize', ['ApplyEllipsis', 'SetContainerHeights', - function (ApplyEllipsis, SetContainerHeights) { - return function (params) { - // Call to set or restore window resize - var group_scope = params.group_scope, - host_scope = params.host_scope; - $(window).off("resize"); - $(window).resize(_.debounce(function() { - ApplyEllipsis('#groups_table .group-name a'); - ApplyEllipsis('#hosts_table .host-name a'); - SetContainerHeights({ - group_scope: group_scope, - host_scope: host_scope, - reloadHosts: true - }); - }, 500)); + /*console.log('window height: ' + $(window).height()); + console.log('main-menu: ' + $('.main-menu').outerHeight()); + console.log('main_tabs: ' + $('#main_tabs').outerHeight()); + console.log('breadcrumbs: ' + $('#breadcrumbs').outerHeight()); + console.log('footer: ' + $('.site-footer').outerHeight()); + console.log('group-breadcrumbs: ' + $('.group-breadcrumbs').outerHeight()); + console.log('searchwidget: ' + $('#groups-container #search-widget-container').outerHeight()); + console.log('group table head: ' + $('#groups_table thead').height()); + console.log('subtotal: ' + ( $(window).height() - $('.main-menu').outerHeight() - $('#main_tabs').outerHeight() - $('#breadcrumbs').outerHeight() - + $('.site-footer').outerHeight() - $('.group-breadcrumbs').outerHeight() - $('#groups-container #search-widget-container').outerHeight() - + $('#groups_table thead').height() )); + */ + var height = $(window).height() - $('#main-menu-container .navbar').outerHeight() - $('#breadcrumbs').outerHeight() - + $('.site-footer').outerHeight() - $('.group-breadcrumbs').outerHeight() - $('#groups-container #search-widget-container').outerHeight() - + $('#groups_table thead').height() - 90; + height = (height < 600) ? 600 : height; + return height; }; - } -]) + }]) -.factory('SaveInventory', ['InventoryForm', 'Rest', 'Alert', 'ProcessErrors', 'LookUpInit', 'OrganizationList', - 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', - function (InventoryForm, Rest, Alert, ProcessErrors, LookUpInit, OrganizationList, GetBasePath, ParseTypeChange, Wait, - ToJSON) { - return function (params) { + .factory('GetHostContainerRows', [ function() { + return function() { + var height, rows; + height = $('#hosts-container .list-well').height() - $('#hosts-constainer .list-well .row').height() - $('#hosts_table thead').height(); + rows = Math.floor(height / 30) - 2; + rows = (rows < 20) ? 20 : rows; + return rows; + }; + }]) - // Save inventory property modifications + .factory('GetGroupContainerRows', [ function() { + return function() { + var height, rows; + height = $('#groups-container .list-table-container').height(); + rows = Math.floor(height / 31) - 2; + rows = (rows < 20) ? 20 : rows; + return rows; + }; + }]) - var scope = params.scope, - form = InventoryForm, - defaultUrl = GetBasePath('inventory'), - fld, json_data, data; + .factory('SetContainerHeights', [ 'GetGroupContainerHeight', 'GetHostContainerRows', function(GetGroupContainerHeight, GetHostContainerRows) { + return function(params) { + var group_scope = params.group_scope, + host_scope = params.host_scope, + reloadHosts = (params.reloadHosts) ? true : false, + height, rows; - Wait('start'); + $('#groups-container .list-table-container').height('auto'); + $('#hosts-container .list-well').height('auto'); + $('#hosts-container .list-table-container').height('auto'); - // Make sure we have valid variable data - json_data = ToJSON(scope.parseType, scope.variables); + setTimeout(function() { + if ($(window).width() > 1210) { + height = GetGroupContainerHeight(); + $('#groups-container .list-table-container').height(height); + $('#hosts-container .list-table-container').height(height); + setTimeout(function() { $('#hosts-container .list-well').height( $('#groups-container .list-well').outerHeight()); }, 500); + } - 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 (reloadHosts) { + // we need ro recalc the the page size + if ($(window).width() > 1210) { + rows = GetHostContainerRows(); + host_scope.host_page_size = rows; + group_scope.group_page_size = rows; + } + else { + // on small screens we go back to the default + host_scope.host_page_size = 20; + group_scope.group_page_size = 20; + } + host_scope.changePageSize('hosts', 'host'); + group_scope.changePageSize('groups', 'group'); + } + }, 100); + + }; + }]) + + + .factory('WatchInventoryWindowResize', ['ApplyEllipsis', 'SetContainerHeights', + function (ApplyEllipsis, SetContainerHeights) { + return function (params) { + // Call to set or restore window resize + var group_scope = params.group_scope, + host_scope = params.host_scope; + $(window).off("resize"); + $(window).resize(_.debounce(function() { + ApplyEllipsis('#groups_table .group-name a'); + ApplyEllipsis('#hosts_table .host-name a'); + SetContainerHeights({ + group_scope: group_scope, + host_scope: host_scope, + reloadHosts: true + }); + }, 500)); + }; + } + ]) + + .factory('SaveInventory', ['InventoryForm', 'Rest', 'Alert', 'ProcessErrors', 'LookUpInit', 'OrganizationList', + 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', + function (InventoryForm, Rest, Alert, ProcessErrors, LookUpInit, 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'); + 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 varaibles. PUT returned status: ' + status - }); + msg: 'Failed to update inventory. 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 }); + + .factory('EditInventoryProperties', ['InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LookUpInit', 'OrganizationList', + 'GetBasePath', 'ParseTypeChange', 'SaveInventory', 'Wait', 'Store', 'SearchInit', 'ParseVariableString', 'CreateDialog', 'TextareaResize', + function (InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LookUpInit, OrganizationList, GetBasePath, ParseTypeChange, SaveInventory, + Wait, Store, SearchInit, ParseVariableString, CreateDialog, TextareaResize) { + return function (params) { + + var parent_scope = params.scope, + inventory_id = params.inventory_id, + generator = GenerateForm, + form = InventoryForm, + master = {}, + //PreviousSearchParams = Store('CurrentSearchParams'), + buttons, + scope = parent_scope.$new(); + + form.well = false; + + generator.inject(form, { + mode: 'edit', + showButtons: false, + showActions: false, + id: 'inventory-edit-modal-dialog', + breadCrumbs: false, + related: false, + scope: scope }); - }; - } -]) + /* Reset form properties. Otherwise it screws up future requests of the Inventories detail page */ + form.well = true; -.factory('EditInventoryProperties', ['InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LookUpInit', 'OrganizationList', - 'GetBasePath', 'ParseTypeChange', 'SaveInventory', 'Wait', 'Store', 'SearchInit', 'ParseVariableString', 'CreateDialog', 'TextareaResize', - function (InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LookUpInit, OrganizationList, GetBasePath, ParseTypeChange, SaveInventory, - Wait, Store, SearchInit, ParseVariableString, CreateDialog, TextareaResize) { - return function (params) { + buttons = [{ + label: "Cancel", + onClick: function() { + scope.cancelModal(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "inventory-edit-cancel-button" + },{ + label: "Save", + onClick: function() { + scope.saveModal(); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "inventory-edit-save-button" + }]; - var parent_scope = params.scope, - inventory_id = params.inventory_id, - generator = GenerateForm, - form = InventoryForm, - master = {}, - //PreviousSearchParams = Store('CurrentSearchParams'), - buttons, - scope = parent_scope.$new(); - - form.well = false; - - generator.inject(form, { - mode: 'edit', - showButtons: false, - showActions: false, - id: 'inventory-edit-modal-dialog', - breadCrumbs: false, - related: false, - scope: scope - }); - - /* Reset form properties. Otherwise it screws up future requests of the Inventories detail page */ - form.well = true; - - buttons = [{ - label: "Cancel", - onClick: function() { - scope.cancelModal(); - }, - icon: "fa-times", - "class": "btn btn-default", - "id": "inventory-edit-cancel-button" - },{ - label: "Save", - onClick: function() { - scope.saveModal(); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "inventory-edit-save-button" - }]; - - CreateDialog({ - scope: scope, - buttons: buttons, - width: 675, - height: 750, - minWidth: 400, - title: 'Inventory Properties', - id: 'inventory-edit-modal-dialog', - clonseOnEscape: false, - onClose: function() { - Wait('stop'); - scope.codeMirror.destroy(); - $('#inventory-edit-modal-dialog').empty(); - }, - onResizeStop: function() { - TextareaResize({ - scope: scope, - textareaId: 'inventory_variables', - modalId: 'inventory-edit-modal-dialog', - formId: 'inventory_form' - }); - }, - beforeDestroy: function() { - if (scope.codeMirror) { + CreateDialog({ + scope: scope, + buttons: buttons, + width: 675, + height: 750, + minWidth: 400, + title: 'Inventory Properties', + id: 'inventory-edit-modal-dialog', + clonseOnEscape: false, + onClose: function() { + Wait('stop'); scope.codeMirror.destroy(); - } - $('#inventory-edit-modal-dialog').empty(); - }, - onOpen: function() { - $('#inventory_name').focus(); - setTimeout(function() { + $('#inventory-edit-modal-dialog').empty(); + }, + onResizeStop: function() { TextareaResize({ scope: scope, textareaId: 'inventory_variables', modalId: 'inventory-edit-modal-dialog', - formId: 'inventory_form', - parse: true + formId: 'inventory_form' }); - }, 300); - }, - callback: 'InventoryEditDialogReady' - }); - - scope.parseType = 'yaml'; - - if (scope.removeInventoryPropertiesLoaded) { - scope.removeInventoryPropertiesLoaded(); - } - scope.removeInventoryPropertiesLoaded = scope.$on('inventoryPropertiesLoaded', function() { - Wait('stop'); - $('#inventory-edit-modal-dialog').dialog('open'); - }); - - scope.formModalActionLabel = 'Save'; - scope.formModalCancelShow = true; - scope.formModalInfo = false; - scope.formModalHeader = 'Inventory Properties'; - - Wait('start'); - Rest.setUrl(GetBasePath('inventory') + inventory_id + '/'); - Rest.get() - .success(function (data) { - var fld; - for (fld in form.fields) { - if (fld === 'variables') { - scope.variables = ParseVariableString(data.variables); - master.variables = scope.variables; - } else if (fld === 'inventory_name') { - scope[fld] = data.name; - master[fld] = scope[fld]; - } else if (fld === 'inventory_description') { - scope[fld] = data.description; - master[fld] = scope[fld]; - } else if (data[fld]) { - scope[fld] = data[fld]; - master[fld] = scope[fld]; + }, + beforeDestroy: function() { + if (scope.codeMirror) { + scope.codeMirror.destroy(); } - 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]; - } - } - - LookUpInit({ - scope: scope, - form: form, - current_item: scope.organization, - list: OrganizationList, - field: 'organization', - input_type: 'radio' - }); - - scope.$emit('inventoryPropertiesLoaded'); - - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status }); + $('#inventory-edit-modal-dialog').empty(); + }, + onOpen: function() { + $('#inventory_name').focus(); + setTimeout(function() { + TextareaResize({ + scope: scope, + textareaId: 'inventory_variables', + modalId: 'inventory-edit-modal-dialog', + formId: 'inventory_form', + parse: true + }); + }, 300); + }, + callback: 'InventoryEditDialogReady' }); - if (scope.removeInventorySaved) { - scope.removeInventorySaved(); - } - scope.removeInventorySaved = scope.$on('InventorySaved', function () { - //$('#form-modal').modal('hide'); - // Restore prior search state - //if (scope.searchCleanp) { - // scope.searchCleanup(); - //} - //SearchInit({ - // scope: parent_scope, - // set: PreviousSearchParams.set, - // list: PreviousSearchParams.list, - // url: PreviousSearchParams.defaultUrl, - // iterator: PreviousSearchParams.iterator, - // sort_order: PreviousSearchParams.sort_order, - // setWidgets: false - //}); - //parent_scope.$emit('RefreshInventories'); - try { - $('#inventory-edit-modal-dialog').dialog('close'); - } - catch(err) { - // ignore - } - parent_scope.$emit('RefreshInventories'); - scope.$destroy(); - }); + scope.parseType = 'yaml'; - scope.cancelModal = function () { - // Restore prior search state - /*if (scope.searchCleanp) { - scope.searchCleanup(); + if (scope.removeInventoryPropertiesLoaded) { + scope.removeInventoryPropertiesLoaded(); } - SearchInit({ - scope: parent_scope, - set: PreviousSearchParams.set, - list: PreviousSearchParams.list, - url: PreviousSearchParams.defaultUrl, - iterator: PreviousSearchParams.iterator, - sort_order: PreviousSearchParams.sort_order, - setWidgets: false - });*/ - try { - $('#inventory-edit-modal-dialog').dialog('close'); + scope.removeInventoryPropertiesLoaded = scope.$on('inventoryPropertiesLoaded', function() { + Wait('stop'); + $('#inventory-edit-modal-dialog').dialog('open'); + }); + + scope.formModalActionLabel = 'Save'; + scope.formModalCancelShow = true; + scope.formModalInfo = false; + scope.formModalHeader = 'Inventory Properties'; + + Wait('start'); + Rest.setUrl(GetBasePath('inventory') + inventory_id + '/'); + Rest.get() + .success(function (data) { + var fld; + for (fld in form.fields) { + if (fld === 'variables') { + scope.variables = ParseVariableString(data.variables); + master.variables = scope.variables; + } else if (fld === 'inventory_name') { + scope[fld] = data.name; + master[fld] = scope[fld]; + } else if (fld === 'inventory_description') { + scope[fld] = data.description; + master[fld] = scope[fld]; + } else if (data[fld]) { + scope[fld] = data[fld]; + master[fld] = scope[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]; + } + } + + LookUpInit({ + scope: scope, + form: form, + current_item: scope.organization, + list: OrganizationList, + field: 'organization', + input_type: 'radio' + }); + + scope.$emit('inventoryPropertiesLoaded'); + + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status }); + }); + + if (scope.removeInventorySaved) { + scope.removeInventorySaved(); } - catch(err) { - // ignore - } - scope.$destroy(); + scope.removeInventorySaved = scope.$on('InventorySaved', function () { + //$('#form-modal').modal('hide'); + // Restore prior search state + //if (scope.searchCleanp) { + // scope.searchCleanup(); + //} + //SearchInit({ + // scope: parent_scope, + // set: PreviousSearchParams.set, + // list: PreviousSearchParams.list, + // url: PreviousSearchParams.defaultUrl, + // iterator: PreviousSearchParams.iterator, + // sort_order: PreviousSearchParams.sort_order, + // setWidgets: false + //}); + //parent_scope.$emit('RefreshInventories'); + try { + $('#inventory-edit-modal-dialog').dialog('close'); + } + catch(err) { + // ignore + } + parent_scope.$emit('RefreshInventories'); + scope.$destroy(); + }); + + scope.cancelModal = function () { + // Restore prior search state + /*if (scope.searchCleanp) { + scope.searchCleanup(); + } + SearchInit({ + scope: parent_scope, + set: PreviousSearchParams.set, + list: PreviousSearchParams.list, + url: PreviousSearchParams.defaultUrl, + iterator: PreviousSearchParams.iterator, + sort_order: PreviousSearchParams.sort_order, + setWidgets: false + });*/ + try { + $('#inventory-edit-modal-dialog').dialog('close'); + } + catch(err) { + // ignore + } + scope.$destroy(); + }; + + scope.saveModal = function () { + scope.inventory_id = inventory_id; + SaveInventory({ scope: scope, parent_scope: parent_scope }); + }; + }; - - scope.saveModal = function () { - scope.inventory_id = inventory_id; - SaveInventory({ scope: scope, parent_scope: parent_scope }); - }; - - }; - } -]); \ No newline at end of file + } + ]); diff --git a/awx/ui/static/js/helpers/md5.js b/awx/ui/static/js/helpers/md5.js index 11682df2cd..0a097d750e 100644 --- a/awx/ui/static/js/helpers/md5.js +++ b/awx/ui/static/js/helpers/md5.js @@ -14,33 +14,33 @@ */ +export default + angular.module('md5Helper', ['RestServices', 'Utilities', 'angular-md5']) + .factory('md5Setup', ['md5', function (md5) { + return function (params) { -angular.module('md5Helper', ['RestServices', 'Utilities', '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; - 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[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.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(); + }; }; - - scope.toggleCallback = function (fld) { - if (scope.allow_callbacks === 'false') { - scope[fld] = ''; - } - }; - - scope.selectAll = function (fld) { - $('input[name="' + fld + '"]').focus().select(); - }; - }; - }]); \ No newline at end of file + }]); diff --git a/awx/ui/static/js/helpers/refresh-related.js b/awx/ui/static/js/helpers/refresh-related.js index d478e46238..fa52fcda66 100644 --- a/awx/ui/static/js/helpers/refresh-related.js +++ b/awx/ui/static/js/helpers/refresh-related.js @@ -20,37 +20,37 @@ */ +export default + angular.module('RefreshRelatedHelper', ['RestServices', 'Utilities', 'PaginationHelpers']) + .factory('RefreshRelated', ['ProcessErrors', 'Rest', 'Wait', 'PageRangeSetup', + function (ProcessErrors, Rest, Wait, PageRangeSetup) { + return function (params) { -angular.module('RefreshRelatedHelper', ['RestServices', 'Utilities', 'PaginationHelpers']) - .factory('RefreshRelated', ['ProcessErrors', 'Rest', 'Wait', 'PageRangeSetup', - function (ProcessErrors, Rest, Wait, PageRangeSetup) { - return function (params) { + var scope = params.scope, + set = params.set, + iterator = params.iterator, + url = params.url; - var scope = params.scope, - set = params.set, - iterator = params.iterator, - url = params.url; - - Rest.setUrl(url); - Rest.get() - .success(function (data) { - PageRangeSetup({ - scope: scope, - count: data.count, - next: data.next, - previous: data.previous, - iterator: iterator + Rest.setUrl(url); + Rest.get() + .success(function (data) { + PageRangeSetup({ + scope: scope, + count: data.count, + next: data.next, + previous: data.previous, + iterator: iterator + }); + scope[set] = data.results; + scope[iterator + 'Loading'] = false; + scope[iterator + 'HoldInput'] = false; + Wait('stop'); + scope.$emit('related' + set); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status }); }); - scope[set] = data.results; - scope[iterator + 'Loading'] = false; - scope[iterator + 'HoldInput'] = false; - Wait('stop'); - scope.$emit('related' + set); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status }); - }); - }; - } - ]); \ No newline at end of file + }; + } + ]); diff --git a/awx/ui/static/js/helpers/refresh.js b/awx/ui/static/js/helpers/refresh.js index 6956649fc1..37f625dcfa 100644 --- a/awx/ui/static/js/helpers/refresh.js +++ b/awx/ui/static/js/helpers/refresh.js @@ -19,49 +19,49 @@ */ +export default + angular.module('RefreshHelper', ['RestServices', 'Utilities', 'PaginationHelpers']) + .factory('Refresh', ['ProcessErrors', 'Rest', 'Wait', 'Empty', 'PageRangeSetup', + function (ProcessErrors, Rest, Wait, Empty, PageRangeSetup) { + return function (params) { -angular.module('RefreshHelper', ['RestServices', 'Utilities', 'PaginationHelpers']) - .factory('Refresh', ['ProcessErrors', 'Rest', 'Wait', 'Empty', 'PageRangeSetup', - function (ProcessErrors, Rest, Wait, Empty, PageRangeSetup) { - return function (params) { + var scope = params.scope, + set = params.set, + iterator = params.iterator, + url = params.url, + deferWaitStop = params.deferWaitStop; - var scope = params.scope, - set = params.set, - iterator = params.iterator, - url = params.url, - deferWaitStop = params.deferWaitStop; - - scope.current_url = url; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - var i, modifier; - PageRangeSetup({ - scope: scope, - count: data.count, - next: data.next, - previous: data.previous, - iterator: iterator + scope.current_url = url; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + var i, modifier; + PageRangeSetup({ + scope: scope, + count: data.count, + next: data.next, + previous: data.previous, + iterator: iterator + }); + for (i = 1; i <= 3; i++) { + modifier = (i === 1) ? '' : i; + scope[iterator + 'HoldInput' + modifier] = false; + } + scope[set] = data.results; + scope[iterator + 'Loading'] = false; + scope[iterator + 'HidePaginator'] = false; + if (!deferWaitStop) { + Wait('stop'); + } + scope.$emit('PostRefresh', set); + }) + .error(function (data, status) { + scope[iterator + 'HoldInput'] = false; + ProcessErrors(scope, data, status, null, { + hdr: 'Error!', + msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status + }); }); - for (i = 1; i <= 3; i++) { - modifier = (i === 1) ? '' : i; - scope[iterator + 'HoldInput' + modifier] = false; - } - scope[set] = data.results; - scope[iterator + 'Loading'] = false; - scope[iterator + 'HidePaginator'] = false; - if (!deferWaitStop) { - Wait('stop'); - } - scope.$emit('PostRefresh', set); - }) - .error(function (data, status) { - scope[iterator + 'HoldInput'] = false; - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', - msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status - }); - }); - }; - } - ]); \ No newline at end of file + }; + } + ]); diff --git a/awx/ui/static/js/helpers/related-search.js b/awx/ui/static/js/helpers/related-search.js index d5541bffd1..ca3a116c75 100644 --- a/awx/ui/static/js/helpers/related-search.js +++ b/awx/ui/static/js/helpers/related-search.js @@ -20,259 +20,259 @@ */ +export default + angular.module('RelatedSearchHelper', ['RestServices', 'Utilities', 'RefreshRelatedHelper']) + .factory('RelatedSearchInit', ['$timeout', 'Alert', 'Rest', 'RefreshRelated', 'Wait', 'Empty', + function ($timeout, Alert, Rest, RefreshRelated, Wait, Empty) { + return function (params) { -angular.module('RelatedSearchHelper', ['RestServices', 'Utilities', 'RefreshRelatedHelper']) - .factory('RelatedSearchInit', ['$timeout', 'Alert', 'Rest', 'RefreshRelated', 'Wait', 'Empty', - function ($timeout, Alert, Rest, RefreshRelated, Wait, Empty) { - return function (params) { - - var scope = params.scope, - relatedSets = params.relatedSets, - form = params.form, f; + var scope = params.scope, + relatedSets = params.relatedSets, + form = params.form, f; - // add 'selected' class to the selected li element - function setSelectedItem(iterator, label) { - $('#' + iterator + 'SearchDropdown' + ' li').each(function() { - $(this).removeClass('selected'); - var link = $(this).find('a'); - if (label === link.text()) { - $(this).addClass('selected'); - } - }); - } - - // Set default values - function setDefaults(inIterator) { - var iterator, f, fld, set; - for (set in form.related) { - if (form.related[set].type !== 'tree' && (inIterator === undefined || inIterator === form.related[set].iterator)) { - iterator = form.related[set].iterator; - for (fld in form.related[set].fields) { - if (form.related[set].fields[fld].key) { - if (form.related[set].fields[fld].searchable === undefined || form.related[set].fields[fld].searchable === true) { - scope[iterator + 'SearchField'] = fld; - scope[iterator + 'SearchFieldLabel'] = form.related[set].fields[fld].label; - } - break; - } + // add 'selected' class to the selected li element + function setSelectedItem(iterator, label) { + $('#' + iterator + 'SearchDropdown' + ' li').each(function() { + $(this).removeClass('selected'); + var link = $(this).find('a'); + if (label === link.text()) { + $(this).addClass('selected'); } + }); + } - if (Empty(scope[iterator + 'SearchField'])) { - // A field marked as key may not be 'searchable'. Find the first searchable field. + // Set default values + function setDefaults(inIterator) { + var iterator, f, fld, set; + for (set in form.related) { + if (form.related[set].type !== 'tree' && (inIterator === undefined || inIterator === form.related[set].iterator)) { + iterator = form.related[set].iterator; for (fld in form.related[set].fields) { - if (form.related[set].fields[fld].searchable === undefined || form.related[set].fields[fld].searchable === true) { - scope[iterator + 'SearchField'] = fld; - scope[iterator + 'SearchFieldLabel'] = form.related[set].fields[fld].label; + if (form.related[set].fields[fld].key) { + if (form.related[set].fields[fld].searchable === undefined || form.related[set].fields[fld].searchable === true) { + scope[iterator + 'SearchField'] = fld; + scope[iterator + 'SearchFieldLabel'] = form.related[set].fields[fld].label; + } break; } } - } - scope[iterator + 'SortOrder'] = null; - scope[iterator + 'SearchType'] = 'icontains'; - scope[iterator + 'SearchTypeLabel'] = 'Contains'; - scope[iterator + 'SearchValue'] = null; - scope[iterator + 'SelectShow'] = false; - //scope[iterator + 'HideSearchType'] = false; - scope[iterator + 'ShowStartBtn'] = true; - scope[iterator + 'HideAllStartBtn'] = false; - - f = scope[iterator + 'SearchField']; - if (form.related[set].fields[f].searchType && - (form.related[set].fields[f].searchType === 'boolean' || form.related[set].fields[f].searchType === 'select')) { - scope[iterator + 'SelectShow'] = true; - scope[iterator + 'SearchSelectOpts'] = form.fields[f].searchOptions; - } - if (form.related[set].fields[f].searchType && form.related[set].fields[f].searchType === 'gtzero') { - scope[iterator + "InputHide"] = true; - } - - setSelectedItem(iterator, scope[iterator + 'SearchFieldLabel']); - } - } - } - - setDefaults(); - - scope.resetSearch = function (iterator) { - setDefaults(iterator); - scope.search(iterator); - }; - - // Functions to handle search widget changes - scope.setSearchField = function (iterator, fld, label) { - - var f, related; - - for (related in form.related) { - if (form.related[related].iterator === iterator) { - f = form.related[related].fields[fld]; - } - } - - scope[iterator + 'SearchFieldLabel'] = label; - scope[iterator + 'SearchField'] = fld; - scope[iterator + 'SearchValue'] = ''; - scope[iterator + 'SelectShow'] = false; - //scope[iterator + 'HideSearchType'] = false; - scope[iterator + 'InputHide'] = false; - scope[iterator + 'ShowStartBtn'] = true; - - if (f.searchType !== undefined && f.searchType === 'gtzero') { - scope[iterator + "InputHide"] = true; - scope[iterator + 'ShowStartBtn'] = false; - } - if (f.searchType !== undefined && (f.searchType === 'boolean' || f.searchType === 'select')) { - scope[iterator + 'SelectShow'] = true; - scope[iterator + 'SearchSelectOpts'] = f.searchOptions; - } - - if (f.searchType !== undefined && f.searchType === 'int') { - //scope[iterator + 'HideSearchType'] = true; - scope[iterator + 'SearchType'] = 'int'; - } - - setSelectedItem(iterator, label); - - scope.search(iterator); - - }; - - scope.setSearchType = function (model, type, label) { - scope[model + 'SearchTypeLabel'] = label; - scope[model + 'SearchType'] = type; - scope.search(model); - }; - - scope.startSearch = function (e, iterator) { - // If use clicks enter while on input field, start the search - if (e.keyCode === 13) { - scope.search(iterator); - } - }; - - scope.search = function (iterator) { - //scope[iterator + 'SearchSpin'] = true; - Wait('start'); - scope[iterator + 'Loading'] = false; - scope[iterator + 'HoldInput'] = true; - - if (scope[iterator + 'SearchValue']) { - // User typed a value in input field - scope[iterator + 'ShowStartBtn'] = false; - } - - if (iterator === 'host') { - if (scope.hostSearchField === 'has_active_failures') { - if (scope.hostSearchSelectValue && scope.hostSearchSelectValue.value === 1) { - scope.hostFailureFilter = true; - } else { - scope.hostFailureFilter = false; - } - } - } - - var fld, key, set, url, sort_order; - for (key in relatedSets) { - if (relatedSets[key].iterator === iterator) { - set = key; - url = relatedSets[key].url; - for (fld in form.related[key].fields) { - if (form.related[key].fields[fld].key) { - if (form.related[key].fields[fld].desc) { - sort_order = '-' + fld; - } else { - sort_order = fld; + if (Empty(scope[iterator + 'SearchField'])) { + // A field marked as key may not be 'searchable'. Find the first searchable field. + for (fld in form.related[set].fields) { + if (form.related[set].fields[fld].searchable === undefined || form.related[set].fields[fld].searchable === true) { + scope[iterator + 'SearchField'] = fld; + scope[iterator + 'SearchFieldLabel'] = form.related[set].fields[fld].label; + break; + } } } + + scope[iterator + 'SortOrder'] = null; + scope[iterator + 'SearchType'] = 'icontains'; + scope[iterator + 'SearchTypeLabel'] = 'Contains'; + scope[iterator + 'SearchValue'] = null; + scope[iterator + 'SelectShow'] = false; + //scope[iterator + 'HideSearchType'] = false; + scope[iterator + 'ShowStartBtn'] = true; + scope[iterator + 'HideAllStartBtn'] = false; + + f = scope[iterator + 'SearchField']; + if (form.related[set].fields[f].searchType && + (form.related[set].fields[f].searchType === 'boolean' || form.related[set].fields[f].searchType === 'select')) { + scope[iterator + 'SelectShow'] = true; + scope[iterator + 'SearchSelectOpts'] = form.fields[f].searchOptions; + } + if (form.related[set].fields[f].searchType && form.related[set].fields[f].searchType === 'gtzero') { + scope[iterator + "InputHide"] = true; + } + + setSelectedItem(iterator, scope[iterator + 'SearchFieldLabel']); } - break; } } - sort_order = (scope[iterator + 'SortOrder'] === null) ? sort_order : scope[iterator + 'SortOrder']; - f = form.related[set].fields[scope[iterator + 'SearchField']]; - if ((scope[iterator + 'SelectShow'] === false && !Empty(scope[iterator + 'SearchValue'])) || - (scope[iterator + 'SelectShow'] && scope[iterator + 'SearchSelectValue']) || - (f.searchType && f.searchType === 'gtzero')) { - if (f.sourceModel) { - // handle fields whose source is a related model e.g. inventories.organization - scope[iterator + 'SearchParams'] = f.sourceModel + '__' + f.sourceField + '__'; - } else if (f.searchField) { - scope[iterator + 'SearchParams'] = f.searchField + '__'; - } else { - scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__'; + setDefaults(); + + scope.resetSearch = function (iterator) { + setDefaults(iterator); + scope.search(iterator); + }; + + // Functions to handle search widget changes + scope.setSearchField = function (iterator, fld, label) { + + var f, related; + + for (related in form.related) { + if (form.related[related].iterator === iterator) { + f = form.related[related].fields[fld]; + } } - if (f.searchType && (f.searchType === 'int' || f.searchType === 'boolean')) { - scope[iterator + 'SearchParams'] += 'int='; - } else if (f.searchType && f.searchType === 'gtzero') { - scope[iterator + 'SearchParams'] += 'gt=0'; - } else { - scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType'] + '='; + scope[iterator + 'SearchFieldLabel'] = label; + scope[iterator + 'SearchField'] = fld; + scope[iterator + 'SearchValue'] = ''; + scope[iterator + 'SelectShow'] = false; + //scope[iterator + 'HideSearchType'] = false; + scope[iterator + 'InputHide'] = false; + scope[iterator + 'ShowStartBtn'] = true; + + if (f.searchType !== undefined && f.searchType === 'gtzero') { + scope[iterator + "InputHide"] = true; + scope[iterator + 'ShowStartBtn'] = false; + } + if (f.searchType !== undefined && (f.searchType === 'boolean' || f.searchType === 'select')) { + scope[iterator + 'SelectShow'] = true; + scope[iterator + 'SearchSelectOpts'] = f.searchOptions; } - if (f.searchType && (f.searchType === 'boolean' || f.searchType === 'select')) { - scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue'].value; - } else if (f.searchType === undefined || f.searchType === 'gtzero') { - scope[iterator + 'SearchParams'] += encodeURI(scope[iterator + 'SearchValue']); + if (f.searchType !== undefined && f.searchType === 'int') { + //scope[iterator + 'HideSearchType'] = true; + scope[iterator + 'SearchType'] = 'int'; } - scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + encodeURI(sort_order) : ''; - } else { - scope[iterator + 'SearchParams'] = (sort_order) ? 'order_by=' + encodeURI(sort_order) : ''; - } - scope[iterator + '_page'] = 1; - url += (url.match(/\/$/)) ? '?' : '&'; - url += scope[iterator + 'SearchParams']; - url += (scope[iterator + '_page_size']) ? '&page_size=' + scope[iterator + '_page_size'] : ""; - RefreshRelated({ scope: scope, set: set, iterator: iterator, url: url }); - }; + setSelectedItem(iterator, label); - scope.sort = function (iterator, fld) { - var sort_order, icon, direction, set; + scope.search(iterator); - // reset sort icons back to 'icon-sort' on all columns - // except the one clicked - $('.' + iterator + ' .list-header').each(function () { - if ($(this).attr('id') !== iterator + '-' + fld + '-header') { - var icon = $(this).find('i'); - icon.attr('class', 'fa fa-sort'); + }; + + scope.setSearchType = function (model, type, label) { + scope[model + 'SearchTypeLabel'] = label; + scope[model + 'SearchType'] = type; + scope.search(model); + }; + + scope.startSearch = function (e, iterator) { + // If use clicks enter while on input field, start the search + if (e.keyCode === 13) { + scope.search(iterator); } - }); + }; - // Toggle the icon for the clicked column - // and set the sort direction - icon = $('#' + iterator + '-' + fld + '-header i'); - direction = ''; - if (icon.hasClass('fa-sort')) { - icon.removeClass('fa-sort'); - icon.addClass('fa-sort-up'); - } else if (icon.hasClass('fa-sort-up')) { - icon.removeClass('fa-sort-up'); - icon.addClass('fa-sort-down'); - direction = '-'; - } else if (icon.hasClass('fa-sort-down')) { - icon.removeClass('fa-sort-down'); - icon.addClass('fa-sort-up'); - } + scope.search = function (iterator) { + //scope[iterator + 'SearchSpin'] = true; + Wait('start'); + scope[iterator + 'Loading'] = false; + scope[iterator + 'HoldInput'] = true; - // Set the sorder order value and call the API to refresh the list with the new order - for (set in form.related) { - if (form.related[set].iterator === iterator) { - if (form.related[set].fields[fld].sourceModel) { - sort_order = direction + form.related[set].fields[fld].sourceModel + '__' + - form.related[set].fields[fld].sourceField; + if (scope[iterator + 'SearchValue']) { + // User typed a value in input field + scope[iterator + 'ShowStartBtn'] = false; + } + + if (iterator === 'host') { + if (scope.hostSearchField === 'has_active_failures') { + if (scope.hostSearchSelectValue && scope.hostSearchSelectValue.value === 1) { + scope.hostFailureFilter = true; + } else { + scope.hostFailureFilter = false; + } + } + } + + var fld, key, set, url, sort_order; + for (key in relatedSets) { + if (relatedSets[key].iterator === iterator) { + set = key; + url = relatedSets[key].url; + for (fld in form.related[key].fields) { + if (form.related[key].fields[fld].key) { + if (form.related[key].fields[fld].desc) { + sort_order = '-' + fld; + } else { + sort_order = fld; + } + } + } + break; + } + } + + sort_order = (scope[iterator + 'SortOrder'] === null) ? sort_order : scope[iterator + 'SortOrder']; + f = form.related[set].fields[scope[iterator + 'SearchField']]; + if ((scope[iterator + 'SelectShow'] === false && !Empty(scope[iterator + 'SearchValue'])) || + (scope[iterator + 'SelectShow'] && scope[iterator + 'SearchSelectValue']) || + (f.searchType && f.searchType === 'gtzero')) { + if (f.sourceModel) { + // handle fields whose source is a related model e.g. inventories.organization + scope[iterator + 'SearchParams'] = f.sourceModel + '__' + f.sourceField + '__'; + } else if (f.searchField) { + scope[iterator + 'SearchParams'] = f.searchField + '__'; } else { - sort_order = direction + fld; + scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__'; + } + + if (f.searchType && (f.searchType === 'int' || f.searchType === 'boolean')) { + scope[iterator + 'SearchParams'] += 'int='; + } else if (f.searchType && f.searchType === 'gtzero') { + scope[iterator + 'SearchParams'] += 'gt=0'; + } else { + scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType'] + '='; + } + + if (f.searchType && (f.searchType === 'boolean' || f.searchType === 'select')) { + scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue'].value; + } else if (f.searchType === undefined || f.searchType === 'gtzero') { + scope[iterator + 'SearchParams'] += encodeURI(scope[iterator + 'SearchValue']); + } + scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + encodeURI(sort_order) : ''; + } else { + scope[iterator + 'SearchParams'] = (sort_order) ? 'order_by=' + encodeURI(sort_order) : ''; + } + scope[iterator + '_page'] = 1; + url += (url.match(/\/$/)) ? '?' : '&'; + url += scope[iterator + 'SearchParams']; + url += (scope[iterator + '_page_size']) ? '&page_size=' + scope[iterator + '_page_size'] : ""; + RefreshRelated({ scope: scope, set: set, iterator: iterator, url: url }); + }; + + + scope.sort = function (iterator, fld) { + var sort_order, icon, direction, set; + + // reset sort icons back to 'icon-sort' on all columns + // except the one clicked + $('.' + iterator + ' .list-header').each(function () { + if ($(this).attr('id') !== iterator + '-' + fld + '-header') { + var icon = $(this).find('i'); + icon.attr('class', 'fa fa-sort'); + } + }); + + // Toggle the icon for the clicked column + // and set the sort direction + icon = $('#' + iterator + '-' + fld + '-header i'); + direction = ''; + if (icon.hasClass('fa-sort')) { + icon.removeClass('fa-sort'); + icon.addClass('fa-sort-up'); + } else if (icon.hasClass('fa-sort-up')) { + icon.removeClass('fa-sort-up'); + icon.addClass('fa-sort-down'); + direction = '-'; + } else if (icon.hasClass('fa-sort-down')) { + icon.removeClass('fa-sort-down'); + icon.addClass('fa-sort-up'); + } + + // Set the sorder order value and call the API to refresh the list with the new order + for (set in form.related) { + if (form.related[set].iterator === iterator) { + if (form.related[set].fields[fld].sourceModel) { + sort_order = direction + form.related[set].fields[fld].sourceModel + '__' + + form.related[set].fields[fld].sourceField; + } else { + sort_order = direction + fld; + } } } - } - scope[iterator + 'SortOrder'] = sort_order; - scope.search(iterator); + scope[iterator + 'SortOrder'] = sort_order; + scope.search(iterator); + }; }; - }; - } - ]); + } + ]); diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js index c64370b7bd..ccac5531b0 100644 --- a/awx/ui/static/js/helpers/search.js +++ b/awx/ui/static/js/helpers/search.js @@ -20,538 +20,538 @@ */ +export default + angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) -angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) + .factory('SearchInit', ['Alert', 'Rest', 'Refresh', '$location', 'GetBasePath', 'Empty', '$timeout', 'Wait', 'Store', + function (Alert, Rest, Refresh, $location, GetBasePath, Empty, $timeout, Wait, Store) { + return function (params) { -.factory('SearchInit', ['Alert', 'Rest', 'Refresh', '$location', 'GetBasePath', 'Empty', '$timeout', 'Wait', 'Store', - function (Alert, Rest, Refresh, $location, GetBasePath, Empty, $timeout, Wait, Store) { - return function (params) { - - var scope = params.scope, - set = params.set, - defaultUrl = params.url, - list = params.list, - iterator = (params.iterator) ? params.iterator : list.iterator, - setWidgets = (params.setWidgets === false) ? false : true, - sort_order = params.sort_order || '', - widgets, i, modifier; + var scope = params.scope, + set = params.set, + defaultUrl = params.url, + list = params.list, + iterator = (params.iterator) ? params.iterator : list.iterator, + setWidgets = (params.setWidgets === false) ? false : true, + sort_order = params.sort_order || '', + widgets, i, modifier; - // add 'selected' class to the selected li element - function setSelectedItem(iterator, label, modifier) { // add 'selected' class to the selected li element - $('#' + iterator + 'SearchDropdown' + modifier + ' li').each(function() { - $(this).removeClass('selected'); - var link = $(this).find('a'); - if (label === link.text()) { - $(this).addClass('selected'); - } - }); - } - - function setDefaults(widget) { - // Set default values - var f, fld, fka, modifier; - modifier = (widget === undefined || widget === 1) ? '' : widget; - scope[iterator + 'SearchField' + modifier] = ''; - scope[iterator + 'SearchFieldLabel' + modifier] = ''; - for (fld in list.fields) { - if (list.fields[fld].searchWidget === undefined && widget === 1 || - list.fields[fld].searchWidget === widget) { - if (list.fields[fld].key) { - if (list.fields[fld].sourceModel) { - fka = list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; - sort_order = (list.fields[fld].desc) ? '-' + fka : fka; - } else { - sort_order = (list.fields[fld].desc) ? '-' + fld : fld; - } - if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) { - scope[iterator + 'SearchField' + modifier] = fld; - scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; - } - break; + function setSelectedItem(iterator, label, modifier) { + // add 'selected' class to the selected li element + $('#' + iterator + 'SearchDropdown' + modifier + ' li').each(function() { + $(this).removeClass('selected'); + var link = $(this).find('a'); + if (label === link.text()) { + $(this).addClass('selected'); } - } + }); } - // Default the search field to 'defaultSearchField', if one exists - for (fld in list.fields) { - if (list.fields[fld].searchWidget === undefined && widget === 1 || - list.fields[fld].searchWidget === widget) { - if (list.fields[fld].defaultSearchField) { - scope[iterator + 'SearchField' + modifier] = fld; - scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; - } - } - } - - // A field marked as key may not be 'searchable', and there might not be a 'defaultSearchField', - // so find the first searchable field. - if (Empty(scope[iterator + 'SearchField' + modifier])) { + function setDefaults(widget) { + // Set default values + var f, fld, fka, modifier; + modifier = (widget === undefined || widget === 1) ? '' : widget; + scope[iterator + 'SearchField' + modifier] = ''; + scope[iterator + 'SearchFieldLabel' + modifier] = ''; for (fld in list.fields) { if (list.fields[fld].searchWidget === undefined && widget === 1 || list.fields[fld].searchWidget === widget) { - if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) { - scope[iterator + 'SearchField' + modifier] = fld; - scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; + if (list.fields[fld].key) { + if (list.fields[fld].sourceModel) { + fka = list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; + sort_order = (list.fields[fld].desc) ? '-' + fka : fka; + } else { + sort_order = (list.fields[fld].desc) ? '-' + fld : fld; + } + if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) { + scope[iterator + 'SearchField' + modifier] = fld; + scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; + } break; } } } - } - scope[iterator + 'SearchType' + modifier] = 'icontains'; - scope[iterator + 'SearchTypeLabel' + modifier] = 'Contains'; - scope[iterator + 'SearchParams' + modifier] = ''; - scope[iterator + 'SearchValue' + modifier] = ''; - scope[iterator + 'SelectShow' + modifier] = false; // show/hide the Select - scope[iterator + 'HideSearchType' + modifier] = false; - scope[iterator + 'InputDisable' + modifier] = false; - scope[iterator + 'ExtraParms' + modifier] = ''; - scope[iterator + 'ShowStartBtn' + modifier] = true; - scope[iterator + 'HideAllStartBtn' + modifier] = false; + // Default the search field to 'defaultSearchField', if one exists + for (fld in list.fields) { + if (list.fields[fld].searchWidget === undefined && widget === 1 || + list.fields[fld].searchWidget === widget) { + if (list.fields[fld].defaultSearchField) { + scope[iterator + 'SearchField' + modifier] = fld; + scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; + } + } + } - if (list.fields[scope[iterator + 'SearchField' + modifier]] && - list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) { - if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) { - // if set to a scope variable - scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]; + // A field marked as key may not be 'searchable', and there might not be a 'defaultSearchField', + // so find the first searchable field. + if (Empty(scope[iterator + 'SearchField' + modifier])) { + for (fld in list.fields) { + if (list.fields[fld].searchWidget === undefined && widget === 1 || + list.fields[fld].searchWidget === widget) { + if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) { + scope[iterator + 'SearchField' + modifier] = fld; + scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; + break; + } + } + } + } + + scope[iterator + 'SearchType' + modifier] = 'icontains'; + scope[iterator + 'SearchTypeLabel' + modifier] = 'Contains'; + scope[iterator + 'SearchParams' + modifier] = ''; + scope[iterator + 'SearchValue' + modifier] = ''; + scope[iterator + 'SelectShow' + modifier] = false; // show/hide the Select + scope[iterator + 'HideSearchType' + modifier] = false; + scope[iterator + 'InputDisable' + modifier] = false; + scope[iterator + 'ExtraParms' + modifier] = ''; + scope[iterator + 'ShowStartBtn' + modifier] = true; + scope[iterator + 'HideAllStartBtn' + modifier] = false; + + if (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) { + if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) { + // if set to a scope variable + scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]; + } else { + // Set to a string value in the list definition + scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder; + } } else { - // Set to a string value in the list definition - scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder; + // Default value + scope[iterator + 'SearchPlaceholder' + modifier] = 'Search'; } - } else { - // Default value - scope[iterator + 'SearchPlaceholder' + modifier] = 'Search'; + + scope[iterator + 'InputDisable' + modifier] = + (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject === 'all') ? true : false; + + f = scope[iterator + 'SearchField' + modifier]; + if (list.fields[f]) { + if (list.fields[f].searchType && (list.fields[f].searchType === 'boolean' || + list.fields[f].searchType === 'select')) { + scope[iterator + 'SelectShow' + modifier] = true; + scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[f].searchOptions; + } + if (list.fields[f].searchType && list.fields[f].searchType === 'int') { + scope[iterator + 'HideSearchType' + modifier] = true; + } + if (list.fields[f].searchType && list.fields[f].searchType === 'gtzero') { + scope[iterator + 'InputHide' + modifier] = true; + } + } + + setSelectedItem(iterator, scope[iterator + 'SearchFieldLabel' + modifier], modifier); } - scope[iterator + 'InputDisable' + modifier] = - (list.fields[scope[iterator + 'SearchField' + modifier]] && - list.fields[scope[iterator + 'SearchField' + modifier]].searchObject === 'all') ? true : false; + if (setWidgets) { + // Set default values for each search widget on the page + widgets = (list.searchWidgets) ? list.searchWidgets : 1; + for (i = 1; i <= widgets; i++) { + modifier = (i === 1) ? '' : i; + if ($('#search-widget-container' + modifier)) { + setDefaults(i); + } + } + } - f = scope[iterator + 'SearchField' + modifier]; - if (list.fields[f]) { - if (list.fields[f].searchType && (list.fields[f].searchType === 'boolean' || - list.fields[f].searchType === 'select')) { + scope[iterator + '_current_search_params'] = { + set: set, + defaultUrl: defaultUrl, + list: list, + iterator: iterator, + sort_order: sort_order + }; + + Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']); + Store('CurrentSearchParams', scope[iterator + '_current_search_params']); // Keeping this around for activity stream + + + // Functions to handle search widget changes + scope.setSearchField = function (iterator, fld, label, widget) { + + var modifier = (widget === undefined || widget === 1) ? '' : widget; + scope[iterator + 'SearchFieldLabel' + modifier] = label; + scope[iterator + 'SearchField' + modifier] = fld; + scope[iterator + 'SearchValue' + modifier] = ''; + scope[iterator + 'SelectShow' + modifier] = false; + scope[iterator + 'HideSearchType' + modifier] = false; + scope[iterator + 'InputHide' + modifier] = false; + scope[iterator + 'SearchType' + modifier] = 'icontains'; + scope[iterator + 'InputDisable' + modifier] = (list.fields[fld].searchObject === 'all') ? true : false; + scope[iterator + 'ShowStartBtn' + modifier] = true; + + if (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) { + if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) { + // if set to a scope variable + scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' + + modifier]].searchPlaceholder]; + } else { + // Set to a string value in the list definition + scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' + + modifier]].searchPlaceholder; + } + } else { + // Default value + scope[iterator + 'SearchPlaceholder' + modifier] = 'Search'; + } + + if (list.fields[fld].searchType && list.fields[fld].searchType === 'gtzero') { + scope[iterator + "InputDisable" + modifier] = true; + scope[iterator + 'ShowStartBtn' + modifier] = false; + scope.search(iterator); + } else if (list.fields[fld].searchSingleValue) { + // Query a specific attribute for one specific value + // searchSingleValue: true + // searchType: 'boolean|int|etc.' + // searchValue: < value to match for boolean use 'true'|'false' > + scope[iterator + 'InputDisable' + modifier] = true; + scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; + // For boolean type, SearchValue must be an object + if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'true') { + scope[iterator + "SearchSelectValue" + modifier] = { + value: 1 + }; + } else if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'false') { + scope[iterator + "SearchSelectValue" + modifier] = { + value: 0 + }; + } else { + scope[iterator + "SearchSelectValue" + modifier] = { + value: list.fields[fld].searchValue + }; + } + scope[iterator + 'ShowStartBtn' + modifier] = false; + } else if (list.fields[fld].searchType === 'in') { + scope[iterator + "SearchType" + modifier] = 'in'; + scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; + scope[iterator + "InputDisable" + modifier] = true; + scope[iterator + 'ShowStartBtn' + modifier] = false; + } else if (list.fields[fld].searchType && (list.fields[fld].searchType === 'boolean' || + list.fields[fld].searchType === 'select' || list.fields[fld].searchType === 'select_or')) { scope[iterator + 'SelectShow' + modifier] = true; - scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[f].searchOptions; + scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[fld].searchOptions; + scope[iterator + 'SearchType' + modifier] = ''; + } else if (list.fields[fld].searchType && list.fields[fld].searchType === 'int') { + //scope[iterator + 'HideSearchType' + modifier] = true; + scope[iterator + 'SearchType' + modifier] = 'int'; + } else if (list.fields[fld].searchType && list.fields[fld].searchType === 'isnull') { + scope[iterator + 'SearchType' + modifier] = 'isnull'; + scope[iterator + 'InputDisable' + modifier] = true; + scope[iterator + 'SearchValue' + modifier] = 'true'; + scope[iterator + 'ShowStartBtn' + modifier] = false; } - if (list.fields[f].searchType && list.fields[f].searchType === 'int') { - scope[iterator + 'HideSearchType' + modifier] = true; - } - if (list.fields[f].searchType && list.fields[f].searchType === 'gtzero') { - scope[iterator + 'InputHide' + modifier] = true; - } - } - setSelectedItem(iterator, scope[iterator + 'SearchFieldLabel' + modifier], modifier); - } + setSelectedItem(iterator, label, modifier); - if (setWidgets) { - // Set default values for each search widget on the page - widgets = (list.searchWidgets) ? list.searchWidgets : 1; - for (i = 1; i <= widgets; i++) { - modifier = (i === 1) ? '' : i; - if ($('#search-widget-container' + modifier)) { + scope.search(iterator); + }; + + scope.resetSearch = function (iterator) { + // Respdond to click of reset button + var i, + widgets = (list.searchWidgets) ? list.searchWidgets : 1; + + for (i = 1; i <= widgets; i++) { + // Clear each search widget setDefaults(i); } - } - } - - scope[iterator + '_current_search_params'] = { - set: set, - defaultUrl: defaultUrl, - list: list, - iterator: iterator, - sort_order: sort_order - }; - - Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']); - Store('CurrentSearchParams', scope[iterator + '_current_search_params']); // Keeping this around for activity stream - - - // Functions to handle search widget changes - scope.setSearchField = function (iterator, fld, label, widget) { - - var modifier = (widget === undefined || widget === 1) ? '' : widget; - scope[iterator + 'SearchFieldLabel' + modifier] = label; - scope[iterator + 'SearchField' + modifier] = fld; - scope[iterator + 'SearchValue' + modifier] = ''; - scope[iterator + 'SelectShow' + modifier] = false; - scope[iterator + 'HideSearchType' + modifier] = false; - scope[iterator + 'InputHide' + modifier] = false; - scope[iterator + 'SearchType' + modifier] = 'icontains'; - scope[iterator + 'InputDisable' + modifier] = (list.fields[fld].searchObject === 'all') ? true : false; - scope[iterator + 'ShowStartBtn' + modifier] = true; - - if (list.fields[scope[iterator + 'SearchField' + modifier]] && - list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) { - if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) { - // if set to a scope variable - scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' + - modifier]].searchPlaceholder]; - } else { - // Set to a string value in the list definition - scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' + - modifier]].searchPlaceholder; - } - } else { - // Default value - scope[iterator + 'SearchPlaceholder' + modifier] = 'Search'; - } - - if (list.fields[fld].searchType && list.fields[fld].searchType === 'gtzero') { - scope[iterator + "InputDisable" + modifier] = true; - scope[iterator + 'ShowStartBtn' + modifier] = false; + // Force removal of search keys from the URL + window.location = '/#' + $location.path(); scope.search(iterator); - } else if (list.fields[fld].searchSingleValue) { - // Query a specific attribute for one specific value - // searchSingleValue: true - // searchType: 'boolean|int|etc.' - // searchValue: < value to match for boolean use 'true'|'false' > - scope[iterator + 'InputDisable' + modifier] = true; - scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; - // For boolean type, SearchValue must be an object - if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'true') { - scope[iterator + "SearchSelectValue" + modifier] = { - value: 1 - }; - } else if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'false') { - scope[iterator + "SearchSelectValue" + modifier] = { - value: 0 - }; - } else { - scope[iterator + "SearchSelectValue" + modifier] = { - value: list.fields[fld].searchValue - }; + }; + + if (scope.removeDoSearch) { + scope.removeDoSearch(); + } + scope.removeDoSearch = scope.$on('doSearch', function (e, iterator, page, load, calcOnly, deferWaitStop) { + // + // Execute the search + // + var url = (calcOnly) ? '' : defaultUrl, + connect; + + if (!calcOnly) { + scope[iterator + 'Loading'] = (load === undefined || load === true) ? true : false; + scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0; } - scope[iterator + 'ShowStartBtn' + modifier] = false; - } else if (list.fields[fld].searchType === 'in') { - scope[iterator + "SearchType" + modifier] = 'in'; - scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; - scope[iterator + "InputDisable" + modifier] = true; - scope[iterator + 'ShowStartBtn' + modifier] = false; - } else if (list.fields[fld].searchType && (list.fields[fld].searchType === 'boolean' || - list.fields[fld].searchType === 'select' || list.fields[fld].searchType === 'select_or')) { - scope[iterator + 'SelectShow' + modifier] = true; - scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[fld].searchOptions; - scope[iterator + 'SearchType' + modifier] = ''; - } else if (list.fields[fld].searchType && list.fields[fld].searchType === 'int') { - //scope[iterator + 'HideSearchType' + modifier] = true; - scope[iterator + 'SearchType' + modifier] = 'int'; - } else if (list.fields[fld].searchType && list.fields[fld].searchType === 'isnull') { - scope[iterator + 'SearchType' + modifier] = 'isnull'; - scope[iterator + 'InputDisable' + modifier] = true; - scope[iterator + 'SearchValue' + modifier] = 'true'; - scope[iterator + 'ShowStartBtn' + modifier] = false; - } - setSelectedItem(iterator, label, modifier); - - scope.search(iterator); - }; - - scope.resetSearch = function (iterator) { - // Respdond to click of reset button - var i, - widgets = (list.searchWidgets) ? list.searchWidgets : 1; - - for (i = 1; i <= widgets; i++) { - // Clear each search widget - setDefaults(i); - } - // Force removal of search keys from the URL - window.location = '/#' + $location.path(); - scope.search(iterator); - }; - - if (scope.removeDoSearch) { - scope.removeDoSearch(); - } - scope.removeDoSearch = scope.$on('doSearch', function (e, iterator, page, load, calcOnly, deferWaitStop) { - // - // Execute the search - // - var url = (calcOnly) ? '' : defaultUrl, - connect; - - if (!calcOnly) { - scope[iterator + 'Loading'] = (load === undefined || load === true) ? true : false; - scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0; - } - - //finalize and execute the query - if (scope[iterator + 'SearchParams']) { - if (/\/$/.test(url)) { - url += '?' + scope[iterator + 'SearchParams']; - } else { - url += '&' + scope[iterator + 'SearchParams']; - } - } - connect = (/\/$/.test(url)) ? '?' : '&'; - url += (scope[iterator + '_page_size']) ? connect + 'page_size=' + scope[iterator + '_page_size'] : ""; - if (page) { - connect = (/\/$/.test(url)) ? '?' : '&'; - url += connect + 'page=' + page; - } - if (scope[iterator + 'ExtraParms']) { - connect = (/\/$/.test(url)) ? '?' : '&'; - url += connect + scope[iterator + 'ExtraParms']; - } - url = url.replace(/\&\&/g, '&').replace(/\?\&/,'?'); - if (calcOnly) { - scope.$emit('searchParamsReady', url); - } - else if (defaultUrl && !/undefined/.test(url)) { - Refresh({ - scope: scope, - set: set, - iterator: iterator, - url: url, - deferWaitStop: deferWaitStop - }); - } - e.stopPropagation(); - }); - - - if (scope.removePrepareSearch) { - scope.removePrepareSearch(); - } - scope.removePrepareSearch = scope.$on('prepareSearch', function (e, iterator, page, load, calcOnly, deferWaitStop, spinner) { - // - // Start building the search key/value pairs. This will process each search widget, if the - // selected field is an object type (used on activity stream). - // - if (spinner) { - Wait('start'); - } - scope[iterator + 'SearchParams'] = ''; - var i, modifier, - widgets = (list.searchWidgets) ? list.searchWidgets : 1; - - for (i = 1; i <= widgets; i++) { - modifier = (i === 1) ? '' : i; - if ($('#search-widget-container' + modifier)) { - if (list.fields[scope[iterator + 'SearchField' + modifier]] && - list.fields[scope[iterator + 'SearchField' + modifier]].searchObject) { - // Search field of object type - if (list.fields[scope[iterator + 'SearchField' + modifier]].searchObject !== 'all') { - // An object type is selected - scope[iterator + 'HideAllStartBtn' + modifier] = false; - if (scope[iterator + 'SearchValue' + modifier]) { - // A search value was entered - scope[iterator + 'ShowStartBtn' + modifier] = false; - if (list.fields[scope[iterator + 'SearchField' + modifier]].searchOnID) { - scope[iterator + 'SearchParams'] += '&' + - list.fields[scope[iterator + 'SearchField' + modifier]].searchObject + - '__id=' + scope[iterator + 'SearchValue' + modifier]; - } else { - scope[iterator + 'SearchParams'] += '&' + - list.fields[scope[iterator + 'SearchField' + modifier]].searchObject + - ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchObject === 'user') ? '__username__icontains=' : '__name__icontains=' ) + - scope[iterator + 'SearchValue' + modifier]; - } - } else { - // Search value is empty - scope[iterator + 'ShowStartBtn' + modifier] = true; - scope[iterator + 'SearchParams'] += '&' + - list.fields[scope[iterator + 'SearchField' + modifier]].searchField + - '=' + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject; - } - } else { - // Object Type set to All - scope[iterator + 'HideAllStartBtn' + modifier] = true; - } - } - } - } - e.stopPropagation(); - scope.$emit('prepareSearch2', iterator, page, load, calcOnly, deferWaitStop); - - }); - - if (scope.removePrepareSearch2) { - scope.removePrepareSearch2(); - } - scope.removePrepareSearch2 = scope.$on('prepareSearch2', function (e, iterator, page, load, calcOnly, deferWaitStop) { - // Continue building the search by examining the remaining search widgets. If we're looking at activity_stream, - // there's more than one. - var i, modifier, - widgets = (list.searchWidgets) ? list.searchWidgets : 1; - - for (i = 1; i <= widgets; i++) { - modifier = (i === 1) ? '' : i; - scope[iterator + 'HoldInput' + modifier] = true; - if ($('#search-widget-container' + modifier) && - list.fields[scope[iterator + 'SearchField' + modifier]] && !list.fields[scope[iterator + 'SearchField' + modifier]].searchObject) { - - // if the search widget exists and its value is not an object, add its parameters to the query - - if (scope[iterator + 'SearchValue' + modifier]) { - // if user typed a value in the input box, show the reset link - scope[iterator + 'ShowStartBtn' + modifier] = false; + //finalize and execute the query + if (scope[iterator + 'SearchParams']) { + if (/\/$/.test(url)) { + url += '?' + scope[iterator + 'SearchParams']; } else { - scope[iterator + 'ShowStartBtn' + modifier] = true; - } - if ((!scope[iterator + 'SelectShow' + modifier] && !Empty(scope[iterator + 'SearchValue' + modifier])) || - (scope[iterator + 'SelectShow' + modifier] && scope[iterator + 'SearchSelectValue' + modifier]) || - (list.fields[scope[iterator + 'SearchField' + modifier]] && - list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero')) { - if (list.fields[scope[iterator + 'SearchField' + modifier]].searchField) { - scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].searchField + '__'; - } else if (list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel) { - // handle fields whose source is a related model e.g. inventories.organization - scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel + '__' + - list.fields[scope[iterator + 'SearchField' + modifier]].sourceField + '__'; - } else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' && - Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) { - scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__'; - } else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' && - !Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) { - scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier]; - } else { - scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__'; - } - - if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && - (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'int' || - list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean')) { - scope[iterator + 'SearchParams'] += 'int='; - } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && - list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero') { - scope[iterator + 'SearchParams'] += 'gt=0'; - } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && - list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'isnull') { - scope[iterator + 'SearchParams'] += 'isnull='; - } else if ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select') && - Empty(scope[iterator + 'SearchSelectValue' + modifier].value) && !/\_\_$/.test(scope[iterator + 'SearchParams']) ) { - scope[iterator + 'SearchParams'] += '=iexact='; - } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'in') { - if (!/\_\_$/.test(scope[iterator + 'SearchParams'])) { - scope[iterator + 'SearchParams'] += '__'; - } - scope[iterator + 'SearchParams'] += 'in='; - } else if (/\_\_$/.test(scope[iterator + 'SearchParams'])) { - scope[iterator + 'SearchParams'] += 'icontains='; - } else { - scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType' + modifier] + '='; - } - - if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && - (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean' || - list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select')) { - scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue' + modifier].value; - } else { - if ((!list.fields[scope[iterator + 'SearchField' + modifier]].searchType) || - (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && - list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'or' && - list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'gtzero')) { - scope[iterator + 'SearchParams'] += encodeURI(scope[iterator + 'SearchValue' + modifier]); - } - } + url += '&' + scope[iterator + 'SearchParams']; } } - } - - if ((iterator === 'inventory' && scope.inventoryFailureFilter) || - (iterator === 'host' && scope.hostFailureFilter)) { - //Things that bypass the search widget. Should go back and add a second widget possibly on - //inventory pages and eliminate this - scope[iterator + 'SearchParams'] += '&has_active_failures=true'; - } - - if (sort_order) { - scope[iterator + 'SearchParams'] += (scope[iterator + 'SearchParams']) ? '&' : ''; - scope[iterator + 'SearchParams'] += 'order_by=' + encodeURI(sort_order); - } - e.stopPropagation(); - scope.$emit('doSearch', iterator, page, load, calcOnly, deferWaitStop); - }); - - scope.startSearch = function (e, iterator) { - // If use clicks enter while on input field, start the search - if (e.keyCode === 13) { - scope.search(iterator); - } - }; - - /** - * Initiate a searh. - * - * @iterator: required, list.iterator value - * @Page: optional. Added to accomodate back function on Job Events detail. - * @Load: optional, set to false if 'Loading' message not desired - * @calcOnly: optional, set to true when you want to calc or figure out search params without executing the search - * @deferWaitStop: optional, when true refresh.js will NOT issue Wait('stop'), thus leaving the spinner. Caller is then - * responsible for stopping the spinner post refresh. - * @spinner: optional, if false, don't show the spinner. - */ - scope.search = function (iterator, page, load, calcOnly, deferWaitStop, spinner) { - page = page || null; - load = (load || !scope[set] || scope[set].length === 0) ? true : false; - calcOnly = (calcOnly) ? true : false; - deferWaitStop = (deferWaitStop) ? true : false; - spinner = (spinner === undefined) ? true : spinner; - if (load) { - scope[set] = []; //clear the list array to make sure 'Loading' is the only thing visible on the list - } - scope.$emit('prepareSearch', iterator, page, load, calcOnly, deferWaitStop, spinner); - }; - - - scope.sort = function (iterator, fld) { - // Reset sort icons back to 'icon-sort' on all columns - // except the one clicked. - $('.list-header').each(function () { - if ($(this).attr('id') !== iterator + '-' + fld + '-header') { - var icon = $(this).find('i'); - icon.attr('class', 'fa fa-sort'); + connect = (/\/$/.test(url)) ? '?' : '&'; + url += (scope[iterator + '_page_size']) ? connect + 'page_size=' + scope[iterator + '_page_size'] : ""; + if (page) { + connect = (/\/$/.test(url)) ? '?' : '&'; + url += connect + 'page=' + page; } + if (scope[iterator + 'ExtraParms']) { + connect = (/\/$/.test(url)) ? '?' : '&'; + url += connect + scope[iterator + 'ExtraParms']; + } + url = url.replace(/\&\&/g, '&').replace(/\?\&/,'?'); + if (calcOnly) { + scope.$emit('searchParamsReady', url); + } + else if (defaultUrl && !/undefined/.test(url)) { + Refresh({ + scope: scope, + set: set, + iterator: iterator, + url: url, + deferWaitStop: deferWaitStop + }); + } + e.stopPropagation(); }); - // Toggle the icon for the clicked column - // and set the sort direction - var icon = $('#' + iterator + '-' + fld + '-header i'), - direction = ''; - if (icon.hasClass('fa-sort')) { - icon.removeClass('fa-sort'); - icon.addClass('fa-sort-up'); - } else if (icon.hasClass('fa-sort-up')) { - icon.removeClass('fa-sort-up'); - icon.addClass('fa-sort-down'); - direction = '-'; - } else if (icon.hasClass('fa-sort-down')) { - icon.removeClass('fa-sort-down'); - icon.addClass('fa-sort-up'); - } - // Set the sorder order value and call the API to refresh the list with the new order - if (list.fields[fld].searchField) { - sort_order = direction + list.fields[fld].searchField; - } else if (list.fields[fld].sortField) { - sort_order = direction + list.fields[fld].sortField; - } else { - if (list.fields[fld].sourceModel) { - sort_order = direction + list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; - } else { - sort_order = direction + fld; + if (scope.removePrepareSearch) { + scope.removePrepareSearch(); + } + scope.removePrepareSearch = scope.$on('prepareSearch', function (e, iterator, page, load, calcOnly, deferWaitStop, spinner) { + // + // Start building the search key/value pairs. This will process each search widget, if the + // selected field is an object type (used on activity stream). + // + if (spinner) { + Wait('start'); } + scope[iterator + 'SearchParams'] = ''; + var i, modifier, + widgets = (list.searchWidgets) ? list.searchWidgets : 1; + + for (i = 1; i <= widgets; i++) { + modifier = (i === 1) ? '' : i; + if ($('#search-widget-container' + modifier)) { + if (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject) { + // Search field of object type + if (list.fields[scope[iterator + 'SearchField' + modifier]].searchObject !== 'all') { + // An object type is selected + scope[iterator + 'HideAllStartBtn' + modifier] = false; + if (scope[iterator + 'SearchValue' + modifier]) { + // A search value was entered + scope[iterator + 'ShowStartBtn' + modifier] = false; + if (list.fields[scope[iterator + 'SearchField' + modifier]].searchOnID) { + scope[iterator + 'SearchParams'] += '&' + + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject + + '__id=' + scope[iterator + 'SearchValue' + modifier]; + } else { + scope[iterator + 'SearchParams'] += '&' + + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject + + ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchObject === 'user') ? '__username__icontains=' : '__name__icontains=' ) + + scope[iterator + 'SearchValue' + modifier]; + } + } else { + // Search value is empty + scope[iterator + 'ShowStartBtn' + modifier] = true; + scope[iterator + 'SearchParams'] += '&' + + list.fields[scope[iterator + 'SearchField' + modifier]].searchField + + '=' + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject; + } + } else { + // Object Type set to All + scope[iterator + 'HideAllStartBtn' + modifier] = true; + } + } + } + } + e.stopPropagation(); + scope.$emit('prepareSearch2', iterator, page, load, calcOnly, deferWaitStop); + + }); + + if (scope.removePrepareSearch2) { + scope.removePrepareSearch2(); } + scope.removePrepareSearch2 = scope.$on('prepareSearch2', function (e, iterator, page, load, calcOnly, deferWaitStop) { + // Continue building the search by examining the remaining search widgets. If we're looking at activity_stream, + // there's more than one. + var i, modifier, + widgets = (list.searchWidgets) ? list.searchWidgets : 1; - scope[list.iterator + '_current_search_params'].sort_order = sort_order; - Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']); + for (i = 1; i <= widgets; i++) { + modifier = (i === 1) ? '' : i; + scope[iterator + 'HoldInput' + modifier] = true; + if ($('#search-widget-container' + modifier) && + list.fields[scope[iterator + 'SearchField' + modifier]] && !list.fields[scope[iterator + 'SearchField' + modifier]].searchObject) { + + // if the search widget exists and its value is not an object, add its parameters to the query + + if (scope[iterator + 'SearchValue' + modifier]) { + // if user typed a value in the input box, show the reset link + scope[iterator + 'ShowStartBtn' + modifier] = false; + } else { + scope[iterator + 'ShowStartBtn' + modifier] = true; + } + if ((!scope[iterator + 'SelectShow' + modifier] && !Empty(scope[iterator + 'SearchValue' + modifier])) || + (scope[iterator + 'SelectShow' + modifier] && scope[iterator + 'SearchSelectValue' + modifier]) || + (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero')) { + if (list.fields[scope[iterator + 'SearchField' + modifier]].searchField) { + scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].searchField + '__'; + } else if (list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel) { + // handle fields whose source is a related model e.g. inventories.organization + scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel + '__' + + list.fields[scope[iterator + 'SearchField' + modifier]].sourceField + '__'; + } else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' && + Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) { + scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__'; + } else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' && + !Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) { + scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier]; + } else { + scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__'; + } + + if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'int' || + list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean')) { + scope[iterator + 'SearchParams'] += 'int='; + } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero') { + scope[iterator + 'SearchParams'] += 'gt=0'; + } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'isnull') { + scope[iterator + 'SearchParams'] += 'isnull='; + } else if ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select') && + Empty(scope[iterator + 'SearchSelectValue' + modifier].value) && !/\_\_$/.test(scope[iterator + 'SearchParams']) ) { + scope[iterator + 'SearchParams'] += '=iexact='; + } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'in') { + if (!/\_\_$/.test(scope[iterator + 'SearchParams'])) { + scope[iterator + 'SearchParams'] += '__'; + } + scope[iterator + 'SearchParams'] += 'in='; + } else if (/\_\_$/.test(scope[iterator + 'SearchParams'])) { + scope[iterator + 'SearchParams'] += 'icontains='; + } else { + scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType' + modifier] + '='; + } + + if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean' || + list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select')) { + scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue' + modifier].value; + } else { + if ((!list.fields[scope[iterator + 'SearchField' + modifier]].searchType) || + (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'or' && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'gtzero')) { + scope[iterator + 'SearchParams'] += encodeURI(scope[iterator + 'SearchValue' + modifier]); + } + } + } + } + } + + if ((iterator === 'inventory' && scope.inventoryFailureFilter) || + (iterator === 'host' && scope.hostFailureFilter)) { + //Things that bypass the search widget. Should go back and add a second widget possibly on + //inventory pages and eliminate this + scope[iterator + 'SearchParams'] += '&has_active_failures=true'; + } + + if (sort_order) { + scope[iterator + 'SearchParams'] += (scope[iterator + 'SearchParams']) ? '&' : ''; + scope[iterator + 'SearchParams'] += 'order_by=' + encodeURI(sort_order); + } + e.stopPropagation(); + scope.$emit('doSearch', iterator, page, load, calcOnly, deferWaitStop); + }); + + scope.startSearch = function (e, iterator) { + // If use clicks enter while on input field, start the search + if (e.keyCode === 13) { + scope.search(iterator); + } + }; + + /** + * Initiate a searh. + * + * @iterator: required, list.iterator value + * @Page: optional. Added to accomodate back function on Job Events detail. + * @Load: optional, set to false if 'Loading' message not desired + * @calcOnly: optional, set to true when you want to calc or figure out search params without executing the search + * @deferWaitStop: optional, when true refresh.js will NOT issue Wait('stop'), thus leaving the spinner. Caller is then + * responsible for stopping the spinner post refresh. + * @spinner: optional, if false, don't show the spinner. + */ + scope.search = function (iterator, page, load, calcOnly, deferWaitStop, spinner) { + page = page || null; + load = (load || !scope[set] || scope[set].length === 0) ? true : false; + calcOnly = (calcOnly) ? true : false; + deferWaitStop = (deferWaitStop) ? true : false; + spinner = (spinner === undefined) ? true : spinner; + if (load) { + scope[set] = []; //clear the list array to make sure 'Loading' is the only thing visible on the list + } + scope.$emit('prepareSearch', iterator, page, load, calcOnly, deferWaitStop, spinner); + }; + + + scope.sort = function (iterator, fld) { + // Reset sort icons back to 'icon-sort' on all columns + // except the one clicked. + $('.list-header').each(function () { + if ($(this).attr('id') !== iterator + '-' + fld + '-header') { + var icon = $(this).find('i'); + icon.attr('class', 'fa fa-sort'); + } + }); + + // Toggle the icon for the clicked column + // and set the sort direction + var icon = $('#' + iterator + '-' + fld + '-header i'), + direction = ''; + if (icon.hasClass('fa-sort')) { + icon.removeClass('fa-sort'); + icon.addClass('fa-sort-up'); + } else if (icon.hasClass('fa-sort-up')) { + icon.removeClass('fa-sort-up'); + icon.addClass('fa-sort-down'); + direction = '-'; + } else if (icon.hasClass('fa-sort-down')) { + icon.removeClass('fa-sort-down'); + icon.addClass('fa-sort-up'); + } + + // Set the sorder order value and call the API to refresh the list with the new order + if (list.fields[fld].searchField) { + sort_order = direction + list.fields[fld].searchField; + } else if (list.fields[fld].sortField) { + sort_order = direction + list.fields[fld].sortField; + } else { + if (list.fields[fld].sourceModel) { + sort_order = direction + list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; + } else { + sort_order = direction + fld; + } + } + + scope[list.iterator + '_current_search_params'].sort_order = sort_order; + Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']); + + scope.search(list.iterator); + }; + + // Call after modal dialogs to remove any lingering callbacks + scope.searchCleanup = function () { + scope.removeDoSearch(); + scope.removePrepareSearch(); + scope.removePrepareSearch2(); + }; - scope.search(list.iterator); }; - - // Call after modal dialogs to remove any lingering callbacks - scope.searchCleanup = function () { - scope.removeDoSearch(); - scope.removePrepareSearch(); - scope.removePrepareSearch2(); - }; - - }; - } -]); \ No newline at end of file + } + ]); diff --git a/awx/ui/static/js/helpers/teams.js b/awx/ui/static/js/helpers/teams.js index 58e2b8100b..5ad7df7bbc 100644 --- a/awx/ui/static/js/helpers/teams.js +++ b/awx/ui/static/js/helpers/teams.js @@ -10,122 +10,122 @@ */ +export default + angular.module('TeamHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', 'SearchHelper', + 'PaginationHelpers', 'ListGenerator' + ]) + .factory('SetTeamListeners', ['Alert', 'Rest', + function (Alert, Rest) { + return function (params) { -angular.module('TeamHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', 'SearchHelper', - 'PaginationHelpers', 'ListGenerator' -]) - .factory('SetTeamListeners', ['Alert', 'Rest', - function (Alert, Rest) { - return function (params) { + var scope = params.scope, + set = params.set, + iterator = params.iterator; - var scope = params.scope, - set = params.set, - iterator = params.iterator; + // Listeners to perform lookups after main inventory list loads - // 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; + 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; + } } } + scope[iterator + 'SearchSpin'] = false; + scope[set] = results; } - 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. + 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; + 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); - }); - } + 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); - } - }); + for (i = 0; i < results.length; i++) { + url = '/api/v1/organizations/' + results[i].organization + '/'; + getOrganization(url); + } + }); + }; + } + ]) + + .factory('TeamLookUpOrganizationInit', ['Alert', 'Rest', 'OrganizationList', 'GenerateList', 'SearchInit', 'PaginateInit', + function (Alert, Rest, OrganizationList, GenerateList, SearchInit, PaginateInit) { + return function (params) { + + var scope = params.scope; + + // Show pop-up to select organization + scope.lookUpOrganization = function () { + var list = OrganizationList, + listGenerator = GenerateList, + listScope = listGenerator.inject(list, { mode: 'lookup', hdr: 'Select Organization' }), + defaultUrl = '/api/v1/organizations/'; + + listScope.selectAction = function () { + var i, found = false; + for (i = 0; i < listScope[list.name].length; i++) { + if (listScope[list.iterator + "_" + listScope[list.name][i].id + "_class"] === "success") { + found = true; + scope.organization = listScope[list.name][i].id; + scope.organization_name = listScope[list.name][i].name; + scope.team_form.$setDirty(); + listGenerator.hide(); + } + } + if (found === false) { + Alert('No Selection', 'Click on a row to select an Organization before clicking the Select button.'); + } + }; + + listScope.toggle_organization = function (id) { + // when user clicks a row, remove 'success' class from all rows except clicked-on row + if (listScope[list.name]) { + for (var i = 0; i < listScope[list.name].length; i++) { + listScope[list.iterator + "_" + listScope[list.name][i].id + "_class"] = ""; + } + } + if (id !== null && id !== undefined) { + listScope[list.iterator + "_" + id + "_class"] = "success"; + } + }; + + SearchInit({ + scope: listScope, + set: list.name, + list: list, + url: defaultUrl + }); + PaginateInit({ + scope: listScope, + list: list, + url: defaultUrl, + mode: 'lookup' + }); + scope.search(list.iterator); + listScope.toggle_organization(scope.organization); + }; }; } - ]) - -.factory('TeamLookUpOrganizationInit', ['Alert', 'Rest', 'OrganizationList', 'GenerateList', 'SearchInit', 'PaginateInit', - function (Alert, Rest, OrganizationList, GenerateList, SearchInit, PaginateInit) { - return function (params) { - - var scope = params.scope; - - // Show pop-up to select organization - scope.lookUpOrganization = function () { - var list = OrganizationList, - listGenerator = GenerateList, - listScope = listGenerator.inject(list, { mode: 'lookup', hdr: 'Select Organization' }), - defaultUrl = '/api/v1/organizations/'; - - listScope.selectAction = function () { - var i, found = false; - for (i = 0; i < listScope[list.name].length; i++) { - if (listScope[list.iterator + "_" + listScope[list.name][i].id + "_class"] === "success") { - found = true; - scope.organization = listScope[list.name][i].id; - scope.organization_name = listScope[list.name][i].name; - scope.team_form.$setDirty(); - listGenerator.hide(); - } - } - if (found === false) { - Alert('No Selection', 'Click on a row to select an Organization before clicking the Select button.'); - } - }; - - listScope.toggle_organization = function (id) { - // when user clicks a row, remove 'success' class from all rows except clicked-on row - if (listScope[list.name]) { - for (var i = 0; i < listScope[list.name].length; i++) { - listScope[list.iterator + "_" + listScope[list.name][i].id + "_class"] = ""; - } - } - if (id !== null && id !== undefined) { - listScope[list.iterator + "_" + id + "_class"] = "success"; - } - }; - - SearchInit({ - scope: listScope, - set: list.name, - list: list, - url: defaultUrl - }); - PaginateInit({ - scope: listScope, - list: list, - url: defaultUrl, - mode: 'lookup' - }); - scope.search(list.iterator); - listScope.toggle_organization(scope.organization); - }; - }; - } -]); \ No newline at end of file + ]); diff --git a/awx/ui/static/js/lists.js b/awx/ui/static/js/lists.js new file mode 100644 index 0000000000..e7f837ba1b --- /dev/null +++ b/awx/ui/static/js/lists.js @@ -0,0 +1,61 @@ +import Admins from "tower/lists/Admins"; +import CloudCredentials from "tower/lists/CloudCredentials"; +import CompletedJobs from "tower/lists/CompletedJobs"; +import ConfigureTowerJobs from "tower/lists/ConfigureTowerJobs"; +import Credentials from "tower/lists/Credentials"; +import CustomInventory from "tower/lists/CustomInventory"; +import Groups from "tower/lists/Groups"; +import HomeGroups from "tower/lists/HomeGroups"; +import HomeHosts from "tower/lists/HomeHosts"; +import Hosts from "tower/lists/Hosts"; +import Inventories from "tower/lists/Inventories"; +import InventoryGroups from "tower/lists/InventoryGroups"; +import InventoryHosts from "tower/lists/InventoryHosts"; +import JobEvents from "tower/lists/JobEvents"; +import JobHosts from "tower/lists/JobHosts"; +import JobTemplates from "tower/lists/JobTemplates"; +import Jobs from "tower/lists/Jobs"; +import Organizations from "tower/lists/Organizations"; +import Permissions from "tower/lists/Permissions"; +import PortalJobTemplates from "tower/lists/PortalJobTemplates"; +import PortalJobs from "tower/lists/PortalJobs"; +import Projects from "tower/lists/Projects"; +import QueuedJobs from "tower/lists/QueuedJobs"; +import RunningJobs from "tower/lists/RunningJobs"; +import ScheduledJobs from "tower/lists/ScheduledJobs"; +import Schedules from "tower/lists/Schedules"; +import Streams from "tower/lists/Streams"; +import Teams from "tower/lists/Teams"; +import Users from "tower/lists/Users"; + +export + { Admins, + CloudCredentials, + CompletedJobs, + ConfigureTowerJobs, + Credentials, + CustomInventory, + Groups, + HomeGroups, + HomeHosts, + Hosts, + Inventories, + InventoryGroups, + InventoryHosts, + JobEvents, + JobHosts, + JobTemplates, + Jobs, + Organizations, + Permissions, + PortalJobTemplates, + PortalJobs, + Projects, + QueuedJobs, + RunningJobs, + ScheduledJobs, + Schedules, + Streams, + Teams, + Users + } diff --git a/awx/ui/static/js/lists/Admins.js b/awx/ui/static/js/lists/Admins.js index 2f92966fbe..bd3e2da532 100644 --- a/awx/ui/static/js/lists/Admins.js +++ b/awx/ui/static/js/lists/Admins.js @@ -9,7 +9,8 @@ -angular.module('AdminListDefinition', []) +export default + angular.module('AdminListDefinition', []) .value('AdminList', { name: 'admins', diff --git a/awx/ui/static/js/lists/CloudCredentials.js b/awx/ui/static/js/lists/CloudCredentials.js index 12b5f52c48..882e47cc82 100644 --- a/awx/ui/static/js/lists/CloudCredentials.js +++ b/awx/ui/static/js/lists/CloudCredentials.js @@ -9,7 +9,8 @@ -angular.module('CloudCredentialsListDefinition', []) +export default + angular.module('CloudCredentialsListDefinition', []) .value('CloudCredentialList', { name: 'cloudcredentials', diff --git a/awx/ui/static/js/lists/CompletedJobs.js b/awx/ui/static/js/lists/CompletedJobs.js index 9d73a64214..dc7fc5e3a0 100644 --- a/awx/ui/static/js/lists/CompletedJobs.js +++ b/awx/ui/static/js/lists/CompletedJobs.js @@ -8,7 +8,8 @@ -angular.module('CompletedJobsDefinition', []) +export default + angular.module('CompletedJobsDefinition', []) .value( 'CompletedJobsList', { name: 'completed_jobs', diff --git a/awx/ui/static/js/lists/ConfigureTowerJobs.js b/awx/ui/static/js/lists/ConfigureTowerJobs.js index 3604d49968..ebd6a4d727 100644 --- a/awx/ui/static/js/lists/ConfigureTowerJobs.js +++ b/awx/ui/static/js/lists/ConfigureTowerJobs.js @@ -9,7 +9,8 @@ -angular.module('ConfigureTowerJobsListDefinition', []) +export default + angular.module('ConfigureTowerJobsListDefinition', []) .value('ConfigureTowerJobsList', { name: 'configure_jobs', diff --git a/awx/ui/static/js/lists/Credentials.js b/awx/ui/static/js/lists/Credentials.js index 28b29cf069..bc2ac87090 100644 --- a/awx/ui/static/js/lists/Credentials.js +++ b/awx/ui/static/js/lists/Credentials.js @@ -9,7 +9,8 @@ -angular.module('CredentialsListDefinition', []) +export default + angular.module('CredentialsListDefinition', []) .value('CredentialList', { name: 'credentials', diff --git a/awx/ui/static/js/lists/CustomInventory.js b/awx/ui/static/js/lists/CustomInventory.js index 3f1ce95974..bba175035a 100644 --- a/awx/ui/static/js/lists/CustomInventory.js +++ b/awx/ui/static/js/lists/CustomInventory.js @@ -9,7 +9,8 @@ -angular.module('CustomInventoryListDefinition', []) +export default + angular.module('CustomInventoryListDefinition', []) .value('CustomInventoryList', { name: 'source_scripts' , // 'custom_inventories', diff --git a/awx/ui/static/js/lists/Groups.js b/awx/ui/static/js/lists/Groups.js index a1f6e43f9c..0f91930d6b 100644 --- a/awx/ui/static/js/lists/Groups.js +++ b/awx/ui/static/js/lists/Groups.js @@ -9,7 +9,8 @@ -angular.module('GroupListDefinition', []) +export default + angular.module('GroupListDefinition', []) .value('GroupList', { name: 'copy_groups', diff --git a/awx/ui/static/js/lists/HomeGroups.js b/awx/ui/static/js/lists/HomeGroups.js index 12d81a0b70..4d6f15a23f 100644 --- a/awx/ui/static/js/lists/HomeGroups.js +++ b/awx/ui/static/js/lists/HomeGroups.js @@ -10,7 +10,8 @@ -angular.module('HomeGroupListDefinition', []) +export default + angular.module('HomeGroupListDefinition', []) .value('HomeGroupList', { name: 'home_groups', diff --git a/awx/ui/static/js/lists/HomeHosts.js b/awx/ui/static/js/lists/HomeHosts.js index c009c2446c..897851ec7a 100644 --- a/awx/ui/static/js/lists/HomeHosts.js +++ b/awx/ui/static/js/lists/HomeHosts.js @@ -10,7 +10,8 @@ -angular.module('HomeHostListDefinition', []) +export default + angular.module('HomeHostListDefinition', []) .value('HomeHostList', { name: 'hosts', diff --git a/awx/ui/static/js/lists/Hosts.js b/awx/ui/static/js/lists/Hosts.js index 81238c9cbd..be43155f53 100644 --- a/awx/ui/static/js/lists/Hosts.js +++ b/awx/ui/static/js/lists/Hosts.js @@ -9,7 +9,8 @@ -angular.module('HostListDefinition', []) +export default + angular.module('HostListDefinition', []) .value('HostList', { name: 'copy_hosts', diff --git a/awx/ui/static/js/lists/Inventories.js b/awx/ui/static/js/lists/Inventories.js index e93948120b..602f7007b4 100644 --- a/awx/ui/static/js/lists/Inventories.js +++ b/awx/ui/static/js/lists/Inventories.js @@ -8,7 +8,8 @@ -angular.module('InventoriesListDefinition', []) +export default + angular.module('InventoriesListDefinition', []) .value('InventoryList', { name: 'inventories', diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js index 7594af6d8d..2e3483d6fa 100644 --- a/awx/ui/static/js/lists/InventoryGroups.js +++ b/awx/ui/static/js/lists/InventoryGroups.js @@ -4,7 +4,8 @@ * InventoryGroups.js * */ -angular.module('InventoryGroupsDefinition', []) +export default + angular.module('InventoryGroupsDefinition', []) .value('InventoryGroups', { name: 'groups', diff --git a/awx/ui/static/js/lists/InventoryHosts.js b/awx/ui/static/js/lists/InventoryHosts.js index aa07d8643d..026fc59a43 100644 --- a/awx/ui/static/js/lists/InventoryHosts.js +++ b/awx/ui/static/js/lists/InventoryHosts.js @@ -9,7 +9,8 @@ -angular.module('InventoryHostsDefinition', []) +export default + angular.module('InventoryHostsDefinition', []) .value('InventoryHosts', { name: 'hosts', diff --git a/awx/ui/static/js/lists/JobEvents.js b/awx/ui/static/js/lists/JobEvents.js index 98d3d2d40b..10248032aa 100644 --- a/awx/ui/static/js/lists/JobEvents.js +++ b/awx/ui/static/js/lists/JobEvents.js @@ -9,7 +9,8 @@ -angular.module('JobEventsListDefinition', []) +export default + angular.module('JobEventsListDefinition', []) .value('JobEventList', { name: 'jobevents', diff --git a/awx/ui/static/js/lists/JobHosts.js b/awx/ui/static/js/lists/JobHosts.js index 2a7e498f5e..1606ef5c65 100644 --- a/awx/ui/static/js/lists/JobHosts.js +++ b/awx/ui/static/js/lists/JobHosts.js @@ -9,7 +9,8 @@ -angular.module('JobHostDefinition', []) +export default + angular.module('JobHostDefinition', []) .value('JobHostList', { name: 'jobhosts', diff --git a/awx/ui/static/js/lists/JobTemplates.js b/awx/ui/static/js/lists/JobTemplates.js index b59622431d..cb71680b58 100644 --- a/awx/ui/static/js/lists/JobTemplates.js +++ b/awx/ui/static/js/lists/JobTemplates.js @@ -9,7 +9,8 @@ -angular.module('JobTemplatesListDefinition', []) +export default + angular.module('JobTemplatesListDefinition', []) .value('JobTemplateList', { name: 'job_templates', diff --git a/awx/ui/static/js/lists/Jobs.js b/awx/ui/static/js/lists/Jobs.js index 9905fce1e2..2ab9b557ed 100644 --- a/awx/ui/static/js/lists/Jobs.js +++ b/awx/ui/static/js/lists/Jobs.js @@ -11,7 +11,8 @@ -angular.module('JobsListDefinition', []) +export default + angular.module('JobsListDefinition', []) .value( 'JobsList', { name: 'jobs', diff --git a/awx/ui/static/js/lists/Organizations.js b/awx/ui/static/js/lists/Organizations.js index bd5aa1d36b..e4f577cf55 100644 --- a/awx/ui/static/js/lists/Organizations.js +++ b/awx/ui/static/js/lists/Organizations.js @@ -9,7 +9,8 @@ -angular.module('OrganizationListDefinition', []) +export default + angular.module('OrganizationListDefinition', []) .value('OrganizationList', { name: 'organizations', diff --git a/awx/ui/static/js/lists/Permissions.js b/awx/ui/static/js/lists/Permissions.js index 4252059761..c9bf5c5689 100644 --- a/awx/ui/static/js/lists/Permissions.js +++ b/awx/ui/static/js/lists/Permissions.js @@ -9,7 +9,8 @@ -angular.module('PermissionListDefinition', []) +export default + angular.module('PermissionListDefinition', []) .value('PermissionList', { name: 'permissions', diff --git a/awx/ui/static/js/lists/PortalJobTemplates.js b/awx/ui/static/js/lists/PortalJobTemplates.js index f01fbc896c..ffceace304 100644 --- a/awx/ui/static/js/lists/PortalJobTemplates.js +++ b/awx/ui/static/js/lists/PortalJobTemplates.js @@ -9,7 +9,8 @@ -angular.module('PortalJobTemplatesListDefinition', []) +export default + angular.module('PortalJobTemplatesListDefinition', []) .value('PortalJobTemplateList', { name: 'job_templates', diff --git a/awx/ui/static/js/lists/PortalJobs.js b/awx/ui/static/js/lists/PortalJobs.js index cc5ca47104..00db6461e0 100644 --- a/awx/ui/static/js/lists/PortalJobs.js +++ b/awx/ui/static/js/lists/PortalJobs.js @@ -11,7 +11,8 @@ -angular.module('PortalJobsListDefinition', []) +export default + angular.module('PortalJobsListDefinition', []) .value( 'PortalJobsList', { name: 'portal_jobs', diff --git a/awx/ui/static/js/lists/Projects.js b/awx/ui/static/js/lists/Projects.js index 9ca356c080..1b8cd0c352 100644 --- a/awx/ui/static/js/lists/Projects.js +++ b/awx/ui/static/js/lists/Projects.js @@ -9,7 +9,8 @@ -angular.module('ProjectsListDefinition', []) +export default + angular.module('ProjectsListDefinition', []) .value('ProjectList', { name: 'projects', diff --git a/awx/ui/static/js/lists/QueuedJobs.js b/awx/ui/static/js/lists/QueuedJobs.js index 60b2b5eabe..c47d12f393 100644 --- a/awx/ui/static/js/lists/QueuedJobs.js +++ b/awx/ui/static/js/lists/QueuedJobs.js @@ -8,7 +8,8 @@ -angular.module('QueuedJobsDefinition', []) +export default + angular.module('QueuedJobsDefinition', []) .value( 'QueuedJobsList', { name: 'queued_jobs', diff --git a/awx/ui/static/js/lists/RunningJobs.js b/awx/ui/static/js/lists/RunningJobs.js index 9171cd8736..81951cb114 100644 --- a/awx/ui/static/js/lists/RunningJobs.js +++ b/awx/ui/static/js/lists/RunningJobs.js @@ -8,7 +8,8 @@ -angular.module('RunningJobsDefinition', []) +export default + angular.module('RunningJobsDefinition', []) .value( 'RunningJobsList', { name: 'running_jobs', diff --git a/awx/ui/static/js/lists/ScheduledJobs.js b/awx/ui/static/js/lists/ScheduledJobs.js index c349bc3228..e8c8628342 100644 --- a/awx/ui/static/js/lists/ScheduledJobs.js +++ b/awx/ui/static/js/lists/ScheduledJobs.js @@ -8,7 +8,8 @@ -angular.module('ScheduledJobsDefinition', []) +export default + angular.module('ScheduledJobsDefinition', []) .value( 'ScheduledJobsList', { name: 'schedules', diff --git a/awx/ui/static/js/lists/Schedules.js b/awx/ui/static/js/lists/Schedules.js index 5cc7756b40..0b4e156bfd 100644 --- a/awx/ui/static/js/lists/Schedules.js +++ b/awx/ui/static/js/lists/Schedules.js @@ -8,7 +8,8 @@ -angular.module('SchedulesListDefinition', []) +export default + angular.module('SchedulesListDefinition', []) .value('SchedulesList', { name: 'schedules', diff --git a/awx/ui/static/js/lists/Streams.js b/awx/ui/static/js/lists/Streams.js index f8c1390f97..7cae0dc263 100644 --- a/awx/ui/static/js/lists/Streams.js +++ b/awx/ui/static/js/lists/Streams.js @@ -9,7 +9,8 @@ -angular.module('StreamListDefinition', []) +export default + angular.module('StreamListDefinition', []) .value('StreamList', { name: 'activities', diff --git a/awx/ui/static/js/lists/Teams.js b/awx/ui/static/js/lists/Teams.js index 353eeb8b01..8540d175f5 100644 --- a/awx/ui/static/js/lists/Teams.js +++ b/awx/ui/static/js/lists/Teams.js @@ -8,7 +8,8 @@ -angular.module('TeamsListDefinition', []) +export default + angular.module('TeamsListDefinition', []) .value('TeamList', { name: 'teams', diff --git a/awx/ui/static/js/lists/Users.js b/awx/ui/static/js/lists/Users.js index 9aabf92638..de539a622f 100644 --- a/awx/ui/static/js/lists/Users.js +++ b/awx/ui/static/js/lists/Users.js @@ -8,7 +8,8 @@ -angular.module('UserListDefinition', []) +export default + angular.module('UserListDefinition', []) .value('UserList', { name: 'users', diff --git a/awx/ui/static/js/services/_data-services.js b/awx/ui/static/js/services/_data-services.js index fb6b4dc4fd..8f61c082c7 100644 --- a/awx/ui/static/js/services/_data-services.js +++ b/awx/ui/static/js/services/_data-services.js @@ -1 +1,7 @@ -angular.module('DataServices', []); +import JobStatusGraphData from 'tower/services/job-status-graph-data'; +import HostCountGraphData from 'tower/services/host-count-graph-data'; + +export default + angular.module('DataServices', ['ApiLoader']) + .service('jobStatusGraphData', JobStatusGraphData) + .service('hostCountGraphData', HostCountGraphData); diff --git a/awx/ui/static/js/services/adjust-graph-size.js b/awx/ui/static/js/services/adjust-graph-size.js index e447f94b38..5a0c5f6fcf 100644 --- a/awx/ui/static/js/services/adjust-graph-size.js +++ b/awx/ui/static/js/services/adjust-graph-size.js @@ -1,5 +1,4 @@ -angular.module('DashboardGraphs'). - factory('adjustGraphSize', function() { +export default function() { // Adjusts the size of graphs based on the current height @@ -87,4 +86,4 @@ angular.module('DashboardGraphs'). chartModel.update(); }; -}); +}; diff --git a/awx/ui/static/js/services/host-count-graph-data.js b/awx/ui/static/js/services/host-count-graph-data.js index db6b2139e9..63ca58fbf0 100644 --- a/awx/ui/static/js/services/host-count-graph-data.js +++ b/awx/ui/static/js/services/host-count-graph-data.js @@ -1,47 +1,48 @@ -angular.module('DataServices') -.service('hostCountGraphData', - ["Rest", - "GetBasePath", - "ProcessErrors", - "$q", - HostCountGraphData]); +HostCountGraphData.$inject = + [ "Rest", + "GetBasePath", + "ProcessErrors", + "$q" + ] -function HostCountGraphData(Rest, getBasePath, processErrors, $q) { +export default + function HostCountGraphData(Rest, getBasePath, processErrors, $q) { - function pluck(property, promise) { - return promise.then(function(value) { - return value[property]; - }); - } - - function getLicenseData() { - var url = getBasePath('config'); - Rest.setUrl(url); - return Rest.get() - .then(function (data){ - var license = data.data.license_info.instance_count; - return license; - }); - } - - function getHostData() { - var url = getBasePath('dashboard')+'graphs/inventory/'; - Rest.setUrl(url); - return pluck('data', Rest.get()); - } - - return { - get: function() { - return $q.all({ - license: getLicenseData(), - hosts: getHostData() - }).catch(function (response) { - var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; - processErrors(null, response.data, response.status, null, { hdr: 'Error!', - msg: errorMessage - }); - return $q.reject(response); + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; }); } - }; -} + + function getLicenseData() { + var url = getBasePath('config'); + Rest.setUrl(url); + return Rest.get() + .then(function (data){ + var license = data.data.license_info.instance_count; + return license; + }); + } + + function getHostData() { + var url = getBasePath('dashboard')+'graphs/inventory/'; + Rest.setUrl(url); + return pluck('data', Rest.get()); + } + + return { + get: function() { + return $q.all({ + license: getLicenseData(), + hosts: getHostData() + }).catch(function (response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + processErrors(null, response.data, response.status, null, { hdr: 'Error!', + msg: errorMessage + }); + return response; + return $q.reject(response); + }); + } + }; + } diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index be3a846ad7..7df93a0271 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -1,60 +1,59 @@ -angular.module('DataServices') -.service('jobStatusGraphData', - ["Rest", - "GetBasePath", - "ProcessErrors", - "$rootScope", - "$q", - JobStatusGraphData]); +export default +["Rest", + "GetBasePath", + "ProcessErrors", + "$rootScope", + "$q", + JobStatusGraphData]; -function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { + function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { - function pluck(property, promise) { - return promise.then(function(value) { - return value[property]; - }); - } - - function getData(period, jobType) { - var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; - Rest.setUrl(url); - var result = Rest.get() - .catch(function(response) { - var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; - - processErrors(null, - response.data, - response.status, - null, { - hdr: 'Error!', - msg: errorMessage - }); - return $q.reject(response); - }); - - return pluck('data', result); - } - - return { - destroyWatcher: angular.noop, - setupWatcher: function(period, jobType) { - this.destroyWatcher = - $rootScope.$on('JobStatusChange', function() { - getData(period, jobType).then(function(result) { - $rootScope. - $broadcast('DataReceived:JobStatusGraph', - result); - return result; - }); + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; }); - }, - get: function(period, jobType) { - - this.destroyWatcher(); - this.setupWatcher(period, jobType); - - return getData(period, jobType); - } - }; -} + + function getData(period, jobType) { + var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; + Rest.setUrl(url); + var result = Rest.get() + .catch(function(response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + + processErrors(null, + response.data, + response.status, + null, { + hdr: 'Error!', + msg: errorMessage + }); + return $q.reject(response); + }); + + return pluck('data', result); + } + + return { + destroyWatcher: angular.noop, + setupWatcher: function(period, jobType) { + this.destroyWatcher = + $rootScope.$on('JobStatusChange', function() { + getData(period, jobType).then(function(result) { + $rootScope. + $broadcast('DataReceived:JobStatusGraph', + result); + return result; + }); + }); + }, + get: function(period, jobType) { + + this.destroyWatcher(); + this.setupWatcher(period, jobType); + + return getData(period, jobType); + + } + }; + } diff --git a/awx/ui/static/js/widgets.js b/awx/ui/static/js/widgets.js new file mode 100644 index 0000000000..6c93a672ee --- /dev/null +++ b/awx/ui/static/js/widgets.js @@ -0,0 +1,25 @@ +import DashboardCounts from "tower/widgets/DashboardCounts"; +import DashboardJobs from "tower/widgets/DashboardJobs"; +import HostGraph from "tower/widgets/HostGraph"; +import HostPieChart from "tower/widgets/HostPieChart"; +import InventorySyncStatus from "tower/widgets/InventorySyncStatus"; +import JobStatus from "tower/widgets/JobStatus"; +import JobStatusGraph from "tower/widgets/JobStatusGraph"; +import ObjectCount from "tower/widgets/ObjectCount"; +import PortalJobs from "tower/widgets/PortalJobs"; +import SCMSyncStatus from "tower/widgets/SCMSyncStatus"; +import Stream from "tower/widgets/Stream"; + +export + { DashboardCounts, + DashboardJobs, + HostGraph, + HostPieChart, + InventorySyncStatus, + JobStatus, + JobStatusGraph, + ObjectCount, + PortalJobs, + SCMSyncStatus, + Stream + } diff --git a/awx/ui/static/lib/angular-md5/angular-md5.js b/awx/ui/static/lib/angular-md5/angular-md5.js index 12b8c11f07..7acad5dc0f 100644 --- a/awx/ui/static/lib/angular-md5/angular-md5.js +++ b/awx/ui/static/lib/angular-md5/angular-md5.js @@ -197,4 +197,4 @@ }; return md5; } ]); -})(this, this.angular, void 0); \ No newline at end of file +})(window, window.angular, void 0); diff --git a/awx/ui/static/lib/d3/.bower.json b/awx/ui/static/lib/d3/.bower.json new file mode 100644 index 0000000000..f7a98ba00b --- /dev/null +++ b/awx/ui/static/lib/d3/.bower.json @@ -0,0 +1,36 @@ +{ + "name": "d3", + "version": "3.5.3", + "main": "d3.js", + "scripts": [ + "d3.js" + ], + "ignore": [ + ".DS_Store", + ".git", + ".gitignore", + ".npmignore", + ".spmignore", + ".travis.yml", + "Makefile", + "bin", + "component.json", + "composer.json", + "index.js", + "lib", + "node_modules", + "package.json", + "src", + "test" + ], + "homepage": "https://github.com/mbostock/d3", + "_release": "3.5.3", + "_resolution": { + "type": "version", + "tag": "v3.5.3", + "commit": "ac59002a6229317a684743f26c3b4b55fc25b569" + }, + "_source": "git://github.com/mbostock/d3.git", + "_target": "*", + "_originalSource": "d3" +} \ No newline at end of file diff --git a/awx/ui/static/lib/d3/CONTRIBUTING.md b/awx/ui/static/lib/d3/CONTRIBUTING.md new file mode 100644 index 0000000000..76126d5f9f --- /dev/null +++ b/awx/ui/static/lib/d3/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +If you’re looking for ways to contribute, please [peruse open issues](https://github.com/mbostock/d3/issues?milestone=&page=1&state=open). The icebox is a good place to find ideas that are not currently in development. If you already have an idea, please check past issues to see whether your idea or a similar one was previously discussed. + +Before submitting a pull request, consider implementing a live example first, say using [bl.ocks.org](http://bl.ocks.org). Real-world use cases go a long way to demonstrating the usefulness of a proposed feature. The more complex a feature’s implementation, the more usefulness it should provide. Share your demo using the #d3js tag on Twitter or by sending it to the d3-js Google group. + +If your proposed feature does not involve changing core functionality, consider submitting it instead as a [D3 plugin](https://github.com/d3/d3-plugins). New core features should be for general use, whereas plugins are suitable for more specialized use cases. When in doubt, it’s easier to start with a plugin before “graduating” to core. + +To contribute new documentation or add examples to the gallery, just [edit the Wiki](https://github.com/mbostock/d3/wiki)! + +## How to Submit a Pull Request + +1. Click the “Fork” button to create your personal fork of the D3 repository. + +2. After cloning your fork of the D3 repository in the terminal, run `npm install` to install D3’s dependencies. + +3. Create a new branch for your new feature. For example: `git checkout -b my-awesome-feature`. A dedicated branch for your pull request means you can develop multiple features at the same time, and ensures that your pull request is stable even if you later decide to develop an unrelated feature. + +4. The `d3.js` and `d3.min.js` files are built from source files in the `src` directory. _Do not edit `d3.js` directly._ Instead, edit the source files, and then run `make` to build the generated files. + +5. Use `make test` to run tests and verify your changes. If you are adding a new feature, you should add new tests! If you are changing existing functionality, make sure the existing tests run, or update them as appropriate. + +6. Sign D3’s [Individual Contributor License Agreement](https://docs.google.com/forms/d/1CzjdBKtDuA8WeuFJinadx956xLQ4Xriv7-oDvXnZMaI/viewform). Unless you are submitting a trivial patch (such as fixing a typo), this form is needed to verify that you are able to contribute. + +7. Submit your pull request, and good luck! diff --git a/awx/ui/static/lib/d3js/build/LICENSE b/awx/ui/static/lib/d3/LICENSE old mode 100755 new mode 100644 similarity index 97% rename from awx/ui/static/lib/d3js/build/LICENSE rename to awx/ui/static/lib/d3/LICENSE index 0bc47f33ef..83013469b9 --- a/awx/ui/static/lib/d3js/build/LICENSE +++ b/awx/ui/static/lib/d3/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013, Michael Bostock +Copyright (c) 2010-2014, Michael Bostock All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/awx/ui/static/lib/d3/README.md b/awx/ui/static/lib/d3/README.md new file mode 100644 index 0000000000..eb334e2701 --- /dev/null +++ b/awx/ui/static/lib/d3/README.md @@ -0,0 +1,9 @@ +# Data-Driven Documents + + + +**D3.js** is a JavaScript library for manipulating documents based on data. **D3** helps you bring data to life using HTML, SVG and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation. + +Want to learn more? [See the wiki.](https://github.com/mbostock/d3/wiki) + +For examples, [see the gallery](https://github.com/mbostock/d3/wiki/Gallery) and [mbostock’s bl.ocks](http://bl.ocks.org/mbostock). diff --git a/awx/ui/static/lib/d3/bower.json b/awx/ui/static/lib/d3/bower.json new file mode 100644 index 0000000000..f07197e3d0 --- /dev/null +++ b/awx/ui/static/lib/d3/bower.json @@ -0,0 +1,26 @@ +{ + "name": "d3", + "version": "3.5.3", + "main": "d3.js", + "scripts": [ + "d3.js" + ], + "ignore": [ + ".DS_Store", + ".git", + ".gitignore", + ".npmignore", + ".spmignore", + ".travis.yml", + "Makefile", + "bin", + "component.json", + "composer.json", + "index.js", + "lib", + "node_modules", + "package.json", + "src", + "test" + ] +} diff --git a/awx/ui/static/lib/d3js/build/d3.v3.js b/awx/ui/static/lib/d3/d3.js old mode 100755 new mode 100644 similarity index 82% rename from awx/ui/static/lib/d3js/build/d3.v3.js rename to awx/ui/static/lib/d3/d3.js index 6264df7daa..81e459fe9c --- a/awx/ui/static/lib/d3js/build/d3.v3.js +++ b/awx/ui/static/lib/d3/d3.js @@ -1,6 +1,6 @@ -d3 = function() { +!function() { var d3 = { - version: "3.3.10" + version: "3.5.3" }; if (!Date.now) Date.now = function() { return +new Date(); @@ -32,19 +32,26 @@ d3 = function() { d3_style_setProperty.call(this, name, value + "", priority); }; } - d3.ascending = function(a, b) { + d3.ascending = d3_ascending; + function d3_ascending(a, b) { return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; - }; + } d3.descending = function(a, b) { return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; }; d3.min = function(array, f) { var i = -1, n = array.length, a, b; if (arguments.length === 1) { - while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } while (++i < n) if ((b = array[i]) != null && a > b) a = b; } else { - while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; } return a; @@ -52,10 +59,16 @@ d3 = function() { d3.max = function(array, f) { var i = -1, n = array.length, a, b; if (arguments.length === 1) { - while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } while (++i < n) if ((b = array[i]) != null && b > a) a = b; } else { - while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; } return a; @@ -63,13 +76,19 @@ d3 = function() { d3.extent = function(array, f) { var i = -1, n = array.length, a, b, c; if (arguments.length === 1) { - while (++i < n && !((a = c = array[i]) != null && a <= a)) a = c = undefined; + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = c = b; + break; + } while (++i < n) if ((b = array[i]) != null) { if (a > b) a = b; if (c < b) c = b; } } else { - while (++i < n && !((a = c = f.call(array, array[i], i)) != null && a <= a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = c = b; + break; + } while (++i < n) if ((b = f.call(array, array[i], i)) != null) { if (a > b) a = b; if (c < b) c = b; @@ -77,44 +96,76 @@ d3 = function() { } return [ a, c ]; }; + function d3_number(x) { + return x === null ? NaN : +x; + } + function d3_numeric(x) { + return !isNaN(x); + } d3.sum = function(array, f) { var s = 0, n = array.length, a, i = -1; if (arguments.length === 1) { - while (++i < n) if (!isNaN(a = +array[i])) s += a; + while (++i < n) if (d3_numeric(a = +array[i])) s += a; } else { - while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; + while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a; } return s; }; - function d3_number(x) { - return x != null && !isNaN(x); - } d3.mean = function(array, f) { - var n = array.length, a, m = 0, i = -1, j = 0; + var s = 0, n = array.length, a, i = -1, j = n; if (arguments.length === 1) { - while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j; } else { - while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j; } - return j ? m : undefined; + if (j) return s / j; }; d3.quantile = function(values, p) { var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; return e ? v + e * (values[h] - v) : v; }; d3.median = function(array, f) { - if (arguments.length > 1) array = array.map(f); - array = array.filter(d3_number); - return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; + var numbers = [], n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a); + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a); + } + if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5); }; - d3.bisector = function(f) { + d3.variance = function(array, f) { + var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) { + if (d3_numeric(a = d3_number(array[i]))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } else { + while (++i < n) { + if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } + if (j > 1) return s / (j - 1); + }; + d3.deviation = function() { + var v = d3.variance.apply(this, arguments); + return v ? Math.sqrt(v) : v; + }; + function d3_bisector(compare) { return { left: function(a, x, lo, hi) { if (arguments.length < 3) lo = 0; if (arguments.length < 4) hi = a.length; while (lo < hi) { var mid = lo + hi >>> 1; - if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; } return lo; }, @@ -123,22 +174,29 @@ d3 = function() { if (arguments.length < 4) hi = a.length; while (lo < hi) { var mid = lo + hi >>> 1; - if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; } return lo; } }; + } + var d3_bisect = d3_bisector(d3_ascending); + d3.bisectLeft = d3_bisect.left; + d3.bisect = d3.bisectRight = d3_bisect.right; + d3.bisector = function(f) { + return d3_bisector(f.length === 1 ? function(d, x) { + return d3_ascending(f(d), x); + } : f); }; - var d3_bisector = d3.bisector(function(d) { - return d; - }); - d3.bisectLeft = d3_bisector.left; - d3.bisect = d3.bisectRight = d3_bisector.right; - d3.shuffle = function(array) { - var m = array.length, t, i; + d3.shuffle = function(array, i0, i1) { + if ((m = arguments.length) < 3) { + i1 = array.length; + if (m < 2) i0 = 0; + } + var m = i1 - i0, t, i; while (m) { i = Math.random() * m-- | 0; - t = array[m], array[m] = array[i], array[i] = t; + t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; } return array; }; @@ -219,72 +277,86 @@ d3 = function() { return k; } function d3_class(ctor, properties) { - try { - for (var key in properties) { - Object.defineProperty(ctor.prototype, key, { - value: properties[key], - enumerable: false - }); - } - } catch (e) { - ctor.prototype = properties; + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); } } - d3.map = function(object) { + d3.map = function(object, f) { var map = new d3_Map(); - if (object instanceof d3_Map) object.forEach(function(key, value) { - map.set(key, value); - }); else for (var key in object) map.set(key, object[key]); + if (object instanceof d3_Map) { + object.forEach(function(key, value) { + map.set(key, value); + }); + } else if (Array.isArray(object)) { + var i = -1, n = object.length, o; + if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); + } else { + for (var key in object) map.set(key, object[key]); + } return map; }; - function d3_Map() {} + function d3_Map() { + this._ = Object.create(null); + } + var d3_map_proto = "__proto__", d3_map_zero = "\x00"; d3_class(d3_Map, { - has: function(key) { - return d3_map_prefix + key in this; - }, + has: d3_map_has, get: function(key) { - return this[d3_map_prefix + key]; + return this._[d3_map_escape(key)]; }, set: function(key, value) { - return this[d3_map_prefix + key] = value; - }, - remove: function(key) { - key = d3_map_prefix + key; - return key in this && delete this[key]; - }, - keys: function() { - var keys = []; - this.forEach(function(key) { - keys.push(key); - }); - return keys; + return this._[d3_map_escape(key)] = value; }, + remove: d3_map_remove, + keys: d3_map_keys, values: function() { var values = []; - this.forEach(function(key, value) { - values.push(value); - }); + for (var key in this._) values.push(this._[key]); return values; }, entries: function() { var entries = []; - this.forEach(function(key, value) { - entries.push({ - key: key, - value: value - }); + for (var key in this._) entries.push({ + key: d3_map_unescape(key), + value: this._[key] }); return entries; }, + size: d3_map_size, + empty: d3_map_empty, forEach: function(f) { - for (var key in this) { - if (key.charCodeAt(0) === d3_map_prefixCode) { - f.call(this, key.substring(1), this[key]); - } - } + for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); } }); - var d3_map_prefix = "\x00", d3_map_prefixCode = d3_map_prefix.charCodeAt(0); + function d3_map_escape(key) { + return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key; + } + function d3_map_unescape(key) { + return (key += "")[0] === d3_map_zero ? key.slice(1) : key; + } + function d3_map_has(key) { + return d3_map_escape(key) in this._; + } + function d3_map_remove(key) { + return (key = d3_map_escape(key)) in this._ && delete this._[key]; + } + function d3_map_keys() { + var keys = []; + for (var key in this._) keys.push(d3_map_unescape(key)); + return keys; + } + function d3_map_size() { + var size = 0; + for (var key in this._) ++size; + return size; + } + function d3_map_empty() { + for (var key in this._) return false; + return true; + } d3.nest = function() { var nest = {}, keys = [], sortKeys = [], sortValues, rollup; function map(mapType, array, depth) { @@ -353,32 +425,21 @@ d3 = function() { if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]); return set; }; - function d3_Set() {} + function d3_Set() { + this._ = Object.create(null); + } d3_class(d3_Set, { - has: function(value) { - return d3_map_prefix + value in this; - }, - add: function(value) { - this[d3_map_prefix + value] = true; - return value; - }, - remove: function(value) { - value = d3_map_prefix + value; - return value in this && delete this[value]; - }, - values: function() { - var values = []; - this.forEach(function(value) { - values.push(value); - }); - return values; + has: d3_map_has, + add: function(key) { + this._[d3_map_escape(key += "")] = true; + return key; }, + remove: d3_map_remove, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, forEach: function(f) { - for (var value in this) { - if (value.charCodeAt(0) === d3_map_prefixCode) { - f.call(this, value.substring(1)); - } - } + for (var key in this._) f.call(this, d3_map_unescape(key)); } }); d3.behavior = {}; @@ -395,7 +456,7 @@ d3 = function() { } function d3_vendorSymbol(object, name) { if (name in object) return name; - name = name.charAt(0).toUpperCase() + name.substring(1); + name = name.charAt(0).toUpperCase() + name.slice(1); for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) { var prefixName = d3_vendorPrefixes[i] + name; if (prefixName in object) return prefixName; @@ -412,8 +473,8 @@ d3 = function() { d3_dispatch.prototype.on = function(type, listener) { var i = type.indexOf("."), name = ""; if (i >= 0) { - name = type.substring(i + 1); - type = type.substring(0, i); + name = type.slice(i + 1); + type = type.slice(0, i); } if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); if (arguments.length === 2) { @@ -488,16 +549,14 @@ d3 = function() { return n.querySelector(s); }, d3_selectAll = function(s, n) { return n.querySelectorAll(s); - }, d3_selectMatcher = d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")], d3_selectMatches = function(n, s) { + }, d3_selectMatcher = d3_documentElement.matches || d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")], d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); }; if (typeof Sizzle === "function") { d3_select = function(s, n) { return Sizzle(s, n)[0] || null; }; - d3_selectAll = function(s, n) { - return Sizzle.uniqueSort(Sizzle(s, n)); - }; + d3_selectAll = Sizzle; d3_selectMatches = Sizzle.matchesSelector; } d3.selection = function() { @@ -556,8 +615,8 @@ d3 = function() { qualify: function(name) { var i = name.indexOf(":"), prefix = name; if (i >= 0) { - prefix = name.substring(0, i); - name = name.substring(i + 1); + prefix = name.slice(0, i); + name = name.slice(i + 1); } return d3_nsPrefix.hasOwnProperty(prefix) ? { space: d3_nsPrefix[prefix], @@ -607,7 +666,7 @@ d3 = function() { d3_selectionPrototype.classed = function(name, value) { if (arguments.length < 2) { if (typeof name === "string") { - var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1; + var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1; if (value = node.classList) { while (++i < n) if (!value.contains(name[i])) return false; } else { @@ -624,8 +683,11 @@ d3 = function() { function d3_selection_classedRe(name) { return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); } + function d3_selection_classes(name) { + return (name + "").trim().split(/^|\s+/); + } function d3_selection_classed(name, value) { - name = name.trim().split(/\s+/).map(d3_selection_classedName); + name = d3_selection_classes(name).map(d3_selection_classedName); var n = name.length; function classedConstant() { var i = -1; @@ -738,11 +800,12 @@ d3 = function() { }); }; d3_selectionPrototype.remove = function() { - return this.each(function() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); - }); + return this.each(d3_selectionRemove); }; + function d3_selectionRemove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } d3_selectionPrototype.data = function(value, key) { var i = -1, n = this.length, group, node; if (!arguments.length) { @@ -757,29 +820,26 @@ d3 = function() { function bind(group, groupData) { var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; if (key) { - var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue; + var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; for (i = -1; ++i < n; ) { - keyValue = key.call(node = group[i], node.__data__, i); - if (nodeByKeyValue.has(keyValue)) { + if (nodeByKeyValue.has(keyValue = key.call(node = group[i], node.__data__, i))) { exitNodes[i] = node; } else { nodeByKeyValue.set(keyValue, node); } - keyValues.push(keyValue); + keyValues[i] = keyValue; } for (i = -1; ++i < m; ) { - keyValue = key.call(groupData, nodeData = groupData[i], i); - if (node = nodeByKeyValue.get(keyValue)) { + if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } else if (node !== true) { updateNodes[i] = node; node.__data__ = nodeData; - } else if (!dataByKeyValue.has(keyValue)) { - enterNodes[i] = d3_selection_dataNode(nodeData); } - dataByKeyValue.set(keyValue, nodeData); - nodeByKeyValue.remove(keyValue); + nodeByKeyValue.set(keyValue, true); } for (i = -1; ++i < n; ) { - if (nodeByKeyValue.has(keyValues[i])) { + if (nodeByKeyValue.get(keyValues[i]) !== true) { exitNodes[i] = group[i]; } } @@ -869,7 +929,7 @@ d3 = function() { return this.order(); }; function d3_selection_sortComparator(comparator) { - if (!arguments.length) comparator = d3.ascending; + if (!arguments.length) comparator = d3_ascending; return function(a, b) { return a && b ? comparator(a.__data__, b.__data__) : !a - !b; }; @@ -906,7 +966,7 @@ d3 = function() { }; d3_selectionPrototype.size = function() { var n = 0; - this.each(function() { + d3_selection_each(this, function() { ++n; }); return n; @@ -954,29 +1014,6 @@ d3 = function() { return node; }; } - d3_selectionPrototype.transition = function() { - var id = d3_transitionInheritId || ++d3_transitionId, subgroups = [], subgroup, node, transition = d3_transitionInherit || { - time: Date.now(), - ease: d3_ease_cubicInOut, - delay: 0, - duration: 250 - }; - for (var j = -1, m = this.length; ++j < m; ) { - subgroups.push(subgroup = []); - for (var group = this[j], i = -1, n = group.length; ++i < n; ) { - if (node = group[i]) d3_transitionNode(node, i, id, transition); - subgroup.push(node); - } - } - return d3_transition(subgroups, id); - }; - d3_selectionPrototype.interrupt = function() { - return this.each(d3_selection_interrupt); - }; - function d3_selection_interrupt() { - var lock = this.__transition__; - if (lock) ++lock.active; - } d3.select = function(node) { var group = [ typeof node === "string" ? d3_select(node, d3_document) : node ]; group.parentNode = d3_documentElement; @@ -1003,7 +1040,7 @@ d3 = function() { }; function d3_selection_on(type, listener, capture) { var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; - if (i > 0) type = type.substring(0, i); + if (i > 0) type = type.slice(0, i); var filter = d3_selection_onFilters.get(type); if (filter) type = filter, wrap = d3_selection_onFilter; function onRemove() { @@ -1070,9 +1107,9 @@ d3 = function() { w.on(name, null); if (d3_event_dragSelect) style[d3_event_dragSelect] = select; if (suppressClick) { - function off() { + var off = function() { w.on(click, null); - } + }; w.on(click, function() { d3_eventPreventDefault(); off(); @@ -1111,55 +1148,51 @@ d3 = function() { var rect = container.getBoundingClientRect(); return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; } - d3.touches = function(container, touches) { - if (arguments.length < 2) touches = d3_eventSource().touches; - return touches ? d3_array(touches).map(function(touch) { - var point = d3_mousePoint(container, touch); - point.identifier = touch.identifier; - return point; - }) : []; + d3.touch = function(container, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches; + if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return d3_mousePoint(container, touch); + } + } }; d3.behavior.drag = function() { - var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, "mousemove", "mouseup"), touchstart = dragstart(touchid, touchposition, "touchmove", "touchend"); + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_behavior_dragMouseSubject, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_behavior_dragTouchSubject, "touchmove", "touchend"); function drag() { this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart); } - function touchid() { - return d3.event.changedTouches[0].identifier; - } - function touchposition(parent, id) { - return d3.touches(parent).filter(function(p) { - return p.identifier === id; - })[0]; - } - function dragstart(id, position, move, end) { + function dragstart(id, position, subject, move, end) { return function() { - var target = this, parent = target.parentNode, event_ = event.of(target, arguments), eventTarget = d3.event.target, eventId = id(), drag = eventId == null ? "drag" : "drag-" + eventId, origin_ = position(parent, eventId), dragged = 0, offset, w = d3.select(d3_window).on(move + "." + drag, moved).on(end + "." + drag, ended), dragRestore = d3_event_dragSuppress(); + var that = this, target = d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject()).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(), position0 = position(parent, dragId); if (origin) { - offset = origin.apply(target, arguments); - offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; + dragOffset = origin.apply(that, arguments); + dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ]; } else { - offset = [ 0, 0 ]; + dragOffset = [ 0, 0 ]; } - event_({ + dispatch({ type: "dragstart" }); function moved() { - var p = position(parent, eventId), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; + var position1 = position(parent, dragId), dx, dy; + if (!position1) return; + dx = position1[0] - position0[0]; + dy = position1[1] - position0[1]; dragged |= dx | dy; - origin_ = p; - event_({ + position0 = position1; + dispatch({ type: "drag", - x: p[0] + offset[0], - y: p[1] + offset[1], + x: position1[0] + dragOffset[0], + y: position1[1] + dragOffset[1], dx: dx, dy: dy }); } function ended() { - w.on(move + "." + drag, null).on(end + "." + drag, null); - dragRestore(dragged && d3.event.target === eventTarget); - event_({ + if (!position(parent, dragId)) return; + dragSubject.on(move + dragName, null).on(end + dragName, null); + dragRestore(dragged && d3.event.target === target); + dispatch({ type: "dragend" }); } @@ -1172,10 +1205,30 @@ d3 = function() { }; return d3.rebind(drag, event, "on"); }; - var π = Math.PI, τ = 2 * π, halfπ = π / 2, ε = 1e-6, ε2 = ε * ε, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_behavior_dragTouchId() { + return d3.event.changedTouches[0].identifier; + } + function d3_behavior_dragTouchSubject() { + return d3.event.target; + } + function d3_behavior_dragMouseSubject() { + return d3_window; + } + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π; function d3_sgn(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; } + function d3_cross2d(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); + } function d3_acos(x) { return x > 1 ? 0 : x < -1 ? π : Math.acos(x); } @@ -1214,13 +1267,13 @@ d3 = function() { x: 0, y: 0, k: 1 - }, translate0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; + }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; function zoom(g) { - g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on(mousemove, mousewheelreset).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); + g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); } zoom.event = function(g) { g.each(function() { - var event_ = event.of(this, arguments), view1 = view; + var dispatch = event.of(this, arguments), view1 = view; if (d3_transitionInheritId) { d3.select(this).transition().each("start.zoom", function() { view = this.__chart__ || { @@ -1228,9 +1281,9 @@ d3 = function() { y: 0, k: 1 }; - zoomstarted(event_); + zoomstarted(dispatch); }).tween("zoom:zoom", function() { - var dx = size[0], dy = size[1], cx = dx / 2, cy = dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); + var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); return function(t) { var l = i(t), k = dx / l[2]; this.__chart__ = view = { @@ -1238,16 +1291,18 @@ d3 = function() { y: cy - l[1] * k, k: k }; - zoomed(event_); + zoomed(dispatch); }; + }).each("interrupt.zoom", function() { + zoomended(dispatch); }).each("end.zoom", function() { - zoomended(event_); + zoomended(dispatch); }); } else { this.__chart__ = view; - zoomstarted(event_); - zoomed(event_); - zoomended(event_); + zoomstarted(dispatch); + zoomed(dispatch); + zoomended(dispatch); } }); }; @@ -1286,6 +1341,11 @@ d3 = function() { size = _ && [ +_[0], +_[1] ]; return zoom; }; + zoom.duration = function(_) { + if (!arguments.length) return duration; + duration = +_; + return zoom; + }; zoom.x = function(z) { if (!arguments.length) return x1; x1 = z; @@ -1322,6 +1382,18 @@ d3 = function() { view.x += p[0] - l[0]; view.y += p[1] - l[1]; } + function zoomTo(that, p, l, k) { + that.__chart__ = { + x: view.x, + y: view.y, + k: view.k + }; + scaleTo(Math.pow(2, k)); + translateTo(center0 = p, l); + that = d3.select(that); + if (duration > 0) that = that.transition().duration(duration); + that.call(zoom.event); + } function rescale() { if (x1) x1.domain(x0.range().map(function(x) { return (x - view.x) / view.k; @@ -1330,46 +1402,47 @@ d3 = function() { return (y - view.y) / view.k; }).map(y0.invert)); } - function zoomstarted(event) { - event({ + function zoomstarted(dispatch) { + if (!zooming++) dispatch({ type: "zoomstart" }); } - function zoomed(event) { + function zoomed(dispatch) { rescale(); - event({ + dispatch({ type: "zoom", scale: view.k, translate: [ view.x, view.y ] }); } - function zoomended(event) { - event({ + function zoomended(dispatch) { + if (!--zooming) dispatch({ type: "zoomend" }); + center0 = null; } function mousedowned() { - var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, dragged = 0, w = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended), l = location(d3.mouse(target)), dragRestore = d3_event_dragSuppress(); - d3_selection_interrupt.call(target); - zoomstarted(event_); + var that = this, target = d3.event.target, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); function moved() { dragged = 1; - translateTo(d3.mouse(target), l); - zoomed(event_); + translateTo(d3.mouse(that), location0); + zoomed(dispatch); } function ended() { - w.on(mousemove, d3_window === target ? mousewheelreset : null).on(mouseup, null); - dragRestore(dragged && d3.event.target === eventTarget); - zoomended(event_); + subject.on(mousemove, null).on(mouseup, null); + dragRestore(dragged && d3.event.target === target); + zoomended(dispatch); } } function touchstarted() { - var target = this, event_ = event.of(target, arguments), locations0 = {}, distance0 = 0, scale0, eventId = d3.event.changedTouches[0].identifier, touchmove = "touchmove.zoom-" + eventId, touchend = "touchend.zoom-" + eventId, w = d3.select(d3_window).on(touchmove, moved).on(touchend, ended), t = d3.select(target).on(mousedown, null).on(touchstart, started), dragRestore = d3_event_dragSuppress(); - d3_selection_interrupt.call(target); + var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(); started(); - zoomstarted(event_); + zoomstarted(dispatch); + subject.on(mousedown, null).on(touchstart, started); function relocate() { - var touches = d3.touches(target); + var touches = d3.touches(that); scale0 = view.k; touches.forEach(function(t) { if (t.identifier in locations0) locations0[t.identifier] = location(t); @@ -1377,6 +1450,9 @@ d3 = function() { return touches; } function started() { + var target = d3.event.target; + d3.select(target).on(touchmove, moved).on(touchend, ended); + targets.push(target); var changed = d3.event.changedTouches; for (var i = 0, n = changed.length; i < n; ++i) { locations0[changed[i].identifier] = null; @@ -1384,11 +1460,9 @@ d3 = function() { var touches = relocate(), now = Date.now(); if (touches.length === 1) { if (now - touchtime < 500) { - var p = touches[0], l = locations0[p.identifier]; - scaleTo(view.k * 2); - translateTo(p, l); + var p = touches[0]; + zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1); d3_eventPreventDefault(); - zoomed(event_); } touchtime = now; } else if (touches.length > 1) { @@ -1397,7 +1471,8 @@ d3 = function() { } } function moved() { - var touches = d3.touches(target), p0, l0, p1, l1; + var touches = d3.touches(that), p0, l0, p1, l1; + d3_selection_interrupt.call(that); for (var i = 0, n = touches.length; i < n; ++i, l1 = null) { p1 = touches[i]; if (l1 = locations0[p1.identifier]) { @@ -1413,7 +1488,7 @@ d3 = function() { } touchtime = null; translateTo(p0, l0); - zoomed(event_); + zoomed(dispatch); } function ended() { if (d3.event.touches.length) { @@ -1425,37 +1500,28 @@ d3 = function() { return void relocate(); } } - w.on(touchmove, null).on(touchend, null); - t.on(mousedown, mousedowned).on(touchstart, touchstarted); + d3.selectAll(targets).on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); dragRestore(); - zoomended(event_); + zoomended(dispatch); } } function mousewheeled() { - var event_ = event.of(this, arguments); - if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), - zoomstarted(event_); + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); else translate0 = location(center0 = center || d3.mouse(this)), + d3_selection_interrupt.call(this), zoomstarted(dispatch); mousewheelTimer = setTimeout(function() { mousewheelTimer = null; - zoomended(event_); + zoomended(dispatch); }, 50); d3_eventPreventDefault(); - var point = center || d3.mouse(this); - if (!translate0) translate0 = location(point); scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); - translateTo(point, translate0); - zoomed(event_); - } - function mousewheelreset() { - translate0 = null; + translateTo(center0, translate0); + zoomed(dispatch); } function dblclicked() { - var event_ = event.of(this, arguments), p = d3.mouse(this), l = location(p), k = Math.log(view.k) / Math.LN2; - zoomstarted(event_); - scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1)); - translateTo(p, l); - zoomed(event_); - zoomended(event_); + var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2; + zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1); } return d3.rebind(zoom, event, "on"); }; @@ -1467,29 +1533,23 @@ d3 = function() { }, "mousewheel") : (d3_behavior_zoomDelta = function() { return -d3.event.detail; }, "MozMousePixelScroll"); - function d3_Color() {} - d3_Color.prototype.toString = function() { + d3.color = d3_color; + function d3_color() {} + d3_color.prototype.toString = function() { return this.rgb() + ""; }; - d3.hsl = function(h, s, l) { - return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l); - }; + d3.hsl = d3_hsl; function d3_hsl(h, s, l) { - return new d3_Hsl(h, s, l); + return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l); } - function d3_Hsl(h, s, l) { - this.h = h; - this.s = s; - this.l = l; - } - var d3_hslPrototype = d3_Hsl.prototype = new d3_Color(); + var d3_hslPrototype = d3_hsl.prototype = new d3_color(); d3_hslPrototype.brighter = function(k) { k = Math.pow(.7, arguments.length ? k : 1); - return d3_hsl(this.h, this.s, this.l / k); + return new d3_hsl(this.h, this.s, this.l / k); }; d3_hslPrototype.darker = function(k) { k = Math.pow(.7, arguments.length ? k : 1); - return d3_hsl(this.h, this.s, k * this.l); + return new d3_hsl(this.h, this.s, k * this.l); }; d3_hslPrototype.rgb = function() { return d3_hsl_rgb(this.h, this.s, this.l); @@ -1511,25 +1571,18 @@ d3 = function() { function vv(h) { return Math.round(v(h) * 255); } - return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); } - d3.hcl = function(h, c, l) { - return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l); - }; + d3.hcl = d3_hcl; function d3_hcl(h, c, l) { - return new d3_Hcl(h, c, l); + return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l); } - function d3_Hcl(h, c, l) { - this.h = h; - this.c = c; - this.l = l; - } - var d3_hclPrototype = d3_Hcl.prototype = new d3_Color(); + var d3_hclPrototype = d3_hcl.prototype = new d3_color(); d3_hclPrototype.brighter = function(k) { - return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); }; d3_hclPrototype.darker = function(k) { - return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); }; d3_hclPrototype.rgb = function() { return d3_hcl_lab(this.h, this.c, this.l).rgb(); @@ -1537,27 +1590,20 @@ d3 = function() { function d3_hcl_lab(h, c, l) { if (isNaN(h)) h = 0; if (isNaN(c)) c = 0; - return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); } - d3.lab = function(l, a, b) { - return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b); - }; + d3.lab = d3_lab; function d3_lab(l, a, b) { - return new d3_Lab(l, a, b); - } - function d3_Lab(l, a, b) { - this.l = l; - this.a = a; - this.b = b; + return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b); } var d3_lab_K = 18; var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; - var d3_labPrototype = d3_Lab.prototype = new d3_Color(); + var d3_labPrototype = d3_lab.prototype = new d3_color(); d3_labPrototype.brighter = function(k) { - return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); }; d3_labPrototype.darker = function(k) { - return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); }; d3_labPrototype.rgb = function() { return d3_lab_rgb(this.l, this.a, this.b); @@ -1567,10 +1613,10 @@ d3 = function() { x = d3_lab_xyz(x) * d3_lab_X; y = d3_lab_xyz(y) * d3_lab_Y; z = d3_lab_xyz(z) * d3_lab_Z; - return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); } function d3_lab_hcl(l, a, b) { - return l > 0 ? d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : d3_hcl(NaN, NaN, l); + return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l); } function d3_lab_xyz(x) { return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; @@ -1581,36 +1627,29 @@ d3 = function() { function d3_xyz_rgb(r) { return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); } - d3.rgb = function(r, g, b) { - return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b); - }; + d3.rgb = d3_rgb; + function d3_rgb(r, g, b) { + return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b); + } function d3_rgbNumber(value) { - return d3_rgb(value >> 16, value >> 8 & 255, value & 255); + return new d3_rgb(value >> 16, value >> 8 & 255, value & 255); } function d3_rgbString(value) { return d3_rgbNumber(value) + ""; } - function d3_rgb(r, g, b) { - return new d3_Rgb(r, g, b); - } - function d3_Rgb(r, g, b) { - this.r = r; - this.g = g; - this.b = b; - } - var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color(); + var d3_rgbPrototype = d3_rgb.prototype = new d3_color(); d3_rgbPrototype.brighter = function(k) { k = Math.pow(.7, arguments.length ? k : 1); var r = this.r, g = this.g, b = this.b, i = 30; - if (!r && !g && !b) return d3_rgb(i, i, i); + if (!r && !g && !b) return new d3_rgb(i, i, i); if (r && r < i) r = i; if (g && g < i) g = i; if (b && b < i) b = i; - return d3_rgb(Math.min(255, ~~(r / k)), Math.min(255, ~~(g / k)), Math.min(255, ~~(b / k))); + return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k)); }; d3_rgbPrototype.darker = function(k) { k = Math.pow(.7, arguments.length ? k : 1); - return d3_rgb(~~(k * this.r), ~~(k * this.g), ~~(k * this.b)); + return new d3_rgb(k * this.r, k * this.g, k * this.b); }; d3_rgbPrototype.hsl = function() { return d3_rgb_hsl(this.r, this.g, this.b); @@ -1622,7 +1661,7 @@ d3 = function() { return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); } function d3_rgb_parse(format, rgb, hsl) { - var r = 0, g = 0, b = 0, m1, m2, name; + var r = 0, g = 0, b = 0, m1, m2, color; m1 = /([a-z]+)\((.*)\)/i.exec(format); if (m1) { m2 = m1[2].split(","); @@ -1638,23 +1677,20 @@ d3 = function() { } } } - if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); - if (format != null && format.charAt(0) === "#") { + if (color = d3_rgb_names.get(format)) return rgb(color.r, color.g, color.b); + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) { if (format.length === 4) { - r = format.charAt(1); - r += r; - g = format.charAt(2); - g += g; - b = format.charAt(3); - b += b; + r = (color & 3840) >> 4; + r = r >> 4 | r; + g = color & 240; + g = g >> 4 | g; + b = color & 15; + b = b << 4 | b; } else if (format.length === 7) { - r = format.substring(1, 3); - g = format.substring(3, 5); - b = format.substring(5, 7); + r = (color & 16711680) >> 16; + g = (color & 65280) >> 8; + b = color & 255; } - r = parseInt(r, 16); - g = parseInt(g, 16); - b = parseInt(b, 16); } return rgb(r, g, b); } @@ -1668,7 +1704,7 @@ d3 = function() { h = NaN; s = l > 0 && l < 1 ? 0 : h; } - return d3_hsl(h, s, l); + return new d3_hsl(h, s, l); } function d3_rgb_lab(r, g, b) { r = d3_rgb_xyz(r); @@ -1861,7 +1897,7 @@ d3 = function() { }; function respond() { var status = request.status, result; - if (!status && request.responseText || status >= 200 && status < 300 || status === 304) { + if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { try { result = response.call(xhr, request); } catch (e) { @@ -1933,15 +1969,19 @@ d3 = function() { callback(error == null ? request : null); } : callback; } + function d3_xhrHasResponse(request) { + var type = request.responseType; + return type && type !== "text" ? request.response : request.responseText; + } d3.dsv = function(delimiter, mimeType) { var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); function dsv(url, row, callback) { if (arguments.length < 3) callback = row, row = null; - var xhr = d3.xhr(url, mimeType, callback); + var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback); xhr.row = function(_) { return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; }; - return xhr.row(row); + return xhr; } function response(request) { return dsv.parse(request.responseText); @@ -1985,7 +2025,7 @@ d3 = function() { } else if (c === 10) { eol = true; } - return text.substring(j + 1, i).replace(/""/g, '"'); + return text.slice(j + 1, i).replace(/""/g, '"'); } while (I < N) { var c = text.charCodeAt(I++), k = 1; @@ -1993,9 +2033,9 @@ d3 = function() { eol = true; if (text.charCodeAt(I) === 10) ++I, ++k; } else if (c !== delimiterCode) continue; - return text.substring(j, I - k); + return text.slice(j, I - k); } - return text.substring(j); + return text.slice(j); } while ((t = token()) !== EOF) { var a = []; @@ -2003,7 +2043,7 @@ d3 = function() { a.push(t); t = token(); } - if (f && !(a = f(a, n++))) continue; + if (f && (a = f(a, n++)) == null) continue; rows.push(a); } return rows; @@ -2097,7 +2137,12 @@ d3 = function() { d3_timer_queueTail = t0; return time; } - var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ], d3_format_currencySymbol = "$"; + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); d3.formatPrefix = function(value, precision) { var i = 0; @@ -2105,7 +2150,7 @@ d3 = function() { if (value < 0) value *= -1; if (precision) value = d3.round(value, d3_format_precision(value, precision)); i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); - i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3)); } return d3_formatPrefixes[8 + i / 3]; }; @@ -2120,79 +2165,97 @@ d3 = function() { symbol: d }; } - d3.round = function(x, n) { - return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); - }; - d3.format = function(specifier) { - var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false; - if (precision) precision = +precision.substring(1); - if (zfill || fill === "0" && align === "=") { - zfill = fill = "0"; - align = "="; - if (comma) width -= Math.floor((width - 1) / 4); - } - switch (type) { - case "n": - comma = true; - type = "g"; - break; - - case "%": - scale = 100; - suffix = "%"; - type = "f"; - break; - - case "p": - scale = 100; - suffix = "%"; - type = "r"; - break; - - case "b": - case "o": - case "x": - case "X": - if (symbol === "#") symbol = "0" + type.toLowerCase(); - - case "c": - case "d": - integer = true; - precision = 0; - break; - - case "s": - scale = -1; - type = "r"; - break; - } - if (symbol === "#") symbol = ""; else if (symbol === "$") symbol = d3_format_currencySymbol; - if (type == "r" && !precision) type = "g"; - if (precision != null) { - if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); - } - type = d3_format_types.get(type) || d3_format_typeDefault; - var zcomma = zfill && comma; - return function(value) { - if (integer && value % 1) return ""; - var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign; - if (scale < 0) { - var prefix = d3.formatPrefix(value, precision); - value = prefix.scale(value); - suffix = prefix.symbol; - } else { - value *= scale; + function d3_locale_numberFormat(locale) { + var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) { + var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = locale_grouping[j = (j + 1) % locale_grouping.length]; } - value = type(value, precision); - var i = value.lastIndexOf("."), before = i < 0 ? value : value.substring(0, i), after = i < 0 ? "" : d3_format_decimalPoint + value.substring(i + 1); - if (!zfill && comma) before = d3_format_group(before); - var length = symbol.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; - if (zcomma) before = d3_format_group(padding + before); - negative += symbol; - value = before + after; - return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + suffix; + return t.reverse().join(locale_thousands); + } : d3_identity; + return function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (symbol === "#") prefix = "0" + type.toLowerCase(); + + case "c": + exponent = false; + + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1]; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + var fullSuffix = suffix; + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign; + if (scale < 0) { + var unit = d3.formatPrefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + value = type(value, precision); + var i = value.lastIndexOf("."), before, after; + if (i < 0) { + var j = exponent ? value.lastIndexOf("e") : -1; + if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j); + } else { + before = value.substring(0, i); + after = locale_decimal + value.substring(i + 1); + } + if (!zfill && comma) before = formatGroup(before, Infinity); + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity); + negative += prefix; + value = before + after; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix; + }; }; - }; + } var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; var d3_format_types = d3.map({ b: function(x) { @@ -2223,24 +2286,522 @@ d3 = function() { return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); } }); - function d3_format_precision(x, p) { - return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); - } function d3_format_typeDefault(x) { return x + ""; } - var d3_format_group = d3_identity; - if (d3_format_grouping) { - var d3_format_groupingLength = d3_format_grouping.length; - d3_format_group = function(value) { - var i = value.length, t = [], j = 0, g = d3_format_grouping[0]; - while (i > 0 && g > 0) { - t.push(value.substring(i -= g, i + g)); - g = d3_format_grouping[j = (j + 1) % d3_format_groupingLength]; + var d3_time = d3.time = {}, d3_date = Date; + function d3_date_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_date_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_date(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_date(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_date = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_date = Date; } - return t.reverse().join(d3_format_thousandsSeparator); }; } + d3_time.year = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3_time.years = d3_time.year.range; + d3_time.years.utc = d3_time.year.utc.range; + d3_time.day = d3_time_interval(function(date) { + var day = new d3_date(2e3, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3_time.days = d3_time.day.range; + d3_time.days.utc = d3_time.day.utc.range; + d3_time.dayOfYear = function(date) { + var year = d3_time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) { + i = 7 - i; + var interval = d3_time[day] = d3_time_interval(function(date) { + (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3_time[day + "s"] = interval.range; + d3_time[day + "s"].utc = interval.utc.range; + d3_time[day + "OfYear"] = function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3_time.week = d3_time.sunday; + d3_time.weeks = d3_time.sunday.range; + d3_time.weeks.utc = d3_time.sunday.utc.range; + d3_time.weekOfYear = d3_time.sundayOfYear; + function d3_locale_timeFormat(locale) { + var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; + function d3_time_format(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.slice(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.slice(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0, + Z: null + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); + if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("w" in d && ("W" in d || "U" in d)) { + date.setFullYear(d.y, 0, 1); + date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); + } else date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L); + return localZ ? date._ : date; + }; + format.toString = function() { + return template; + }; + return format; + } + function d3_time_parse(date, template, string, j) { + var c, p, t, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + t = template.charAt(i++); + p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + d3_time_format.utc = function(template) { + var local = d3_time_format(template); + function format(date) { + try { + d3_date = d3_date_utc; + var utc = new d3_date(); + utc._ = date; + return local(utc); + } finally { + d3_date = Date; + } + } + format.parse = function(string) { + try { + d3_date = d3_date_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_date = Date; + } + }; + format.toString = local.toString; + return format; + }; + d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti; + var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths); + locale_periods.forEach(function(p, i) { + d3_time_periodLookup.set(p.toLowerCase(), i); + }); + var d3_time_formats = { + a: function(d) { + return locale_shortDays[d.getDay()]; + }, + A: function(d) { + return locale_days[d.getDay()]; + }, + b: function(d) { + return locale_shortMonths[d.getMonth()]; + }, + B: function(d) { + return locale_months[d.getMonth()]; + }, + c: d3_time_format(locale_dateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return locale_periods[+(d.getHours() >= 12)]; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); + }, + x: d3_time_format(locale_date), + X: d3_time_format(locale_time), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + j: d3_time_parseDayOfYear, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + U: d3_time_parseWeekNumberSunday, + w: d3_time_parseWeekdayNumber, + W: d3_time_parseWeekNumberMonday, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear, + Z: d3_time_parseZone, + "%": d3_time_parseLiteralPercent + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + return d3_time_format; + } + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/; + function d3_time_formatPad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 1)); + return n ? (date.w = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberSunday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.U = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberMonday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.W = +n[0], i + n[0].length) : -1; + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 4)); + return n ? (date.y = +n[0], i + n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; + } + function d3_time_parseZone(date, string, i) { + return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, + i + 5) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.m = n[0] - 1, i + n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.d = +n[0], i + n[0].length) : -1; + } + function d3_time_parseDayOfYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.j = +n[0], i + n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.H = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.M = +n[0], i + n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.S = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.L = +n[0], i + n[0].length) : -1; + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + function d3_time_parseLiteralPercent(date, string, i) { + d3_time_percentRe.lastIndex = 0; + var n = d3_time_percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function d3_time_formatMulti(formats) { + var n = formats.length, i = -1; + while (++i < n) formats[i][0] = this(formats[i][0]); + return function(date) { + var i = 0, f = formats[i]; + while (!f[1](date)) f = formats[++i]; + return f[0](date); + }; + } + d3.locale = function(locale) { + return { + numberFormat: d3_locale_numberFormat(locale), + timeFormat: d3_locale_timeFormat(locale) + }; + }; + var d3_locale_enUS = d3.locale({ + decimal: ".", + thousands: ",", + grouping: [ 3 ], + currency: [ "$", "" ], + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: [ "AM", "PM" ], + days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], + shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] + }); + d3.format = d3_locale_enUS.numberFormat; d3.geo = {}; function d3_adder() {} d3_adder.prototype = { @@ -2360,7 +2921,7 @@ d3 = function() { function nextPoint(λ, φ) { λ *= d3_radians; φ = φ * d3_radians / 2 + π / 4; - var dλ = λ - λ0, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(dλ), v = k * Math.sin(dλ); + var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ); d3_geo_areaRingSum.add(Math.atan2(v, u)); λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; } @@ -2616,6 +3177,15 @@ d3 = function() { d3_geo_centroidPointXYZ(x0, y0, z0); } } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } function d3_true() { return true; } @@ -2710,7 +3280,6 @@ d3 = function() { clip.lineEnd = ringEnd; segments = []; polygon = []; - listener.polygonStart(); }, polygonEnd: function() { clip.point = point; @@ -2719,13 +3288,15 @@ d3 = function() { segments = d3.merge(segments); var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); if (segments.length) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener); } else if (clipStartInside) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; listener.lineStart(); interpolate(null, null, 1, listener); listener.lineEnd(); } - listener.polygonEnd(); + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; segments = polygon = null; }, sphere: function() { @@ -2753,7 +3324,7 @@ d3 = function() { line.lineEnd(); } var segments; - var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygon, ring; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring; function pointRing(λ, φ) { ring.push([ λ, φ ]); var point = rotate(λ, φ); @@ -2774,9 +3345,12 @@ d3 = function() { if (clean & 1) { segment = ringSegments[0]; var n = segment.length - 1, i = -1, point; - listener.lineStart(); - while (++i < n) listener.point((point = segment[i])[0], point[1]); - listener.lineEnd(); + if (n > 0) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + } return; } if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); @@ -2812,35 +3386,6 @@ d3 = function() { function d3_geo_clipSort(a, b) { return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]); } - function d3_geo_pointInPolygon(point, polygon) { - var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; - d3_geo_areaRingSum.reset(); - for (var i = 0, n = polygon.length; i < n; ++i) { - var ring = polygon[i], m = ring.length; - if (!m) continue; - var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; - while (true) { - if (j === m) j = 0; - point = ring[j]; - var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, antimeridian = abs(dλ) > π, k = sinφ0 * sinφ; - d3_geo_areaRingSum.add(Math.atan2(k * Math.sin(dλ), cosφ0 * cosφ + k * Math.cos(dλ))); - polarAngle += antimeridian ? dλ + (dλ >= 0 ? τ : -τ) : dλ; - if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { - var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); - d3_geo_cartesianNormalize(arc); - var intersection = d3_geo_cartesianCross(meridianNormal, arc); - d3_geo_cartesianNormalize(intersection); - var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); - if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { - winding += antimeridian ^ dλ >= 0 ? 1 : -1; - } - } - if (!j++) break; - λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; - } - } - return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ winding & 1; - } var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]); function d3_geo_clipAntimeridianLine(listener) { var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; @@ -2908,6 +3453,35 @@ d3 = function() { listener.point(to[0], to[1]); } } + function d3_geo_pointInPolygon(point, polygon) { + var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; + d3_geo_areaRingSum.reset(); + for (var i = 0, n = polygon.length; i < n; ++i) { + var ring = polygon[i], m = ring.length; + if (!m) continue; + var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; + while (true) { + if (j === m) j = 0; + point = ring[j]; + var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ; + d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ))); + polarAngle += antimeridian ? dλ + sdλ * τ : dλ; + if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { + var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); + d3_geo_cartesianNormalize(arc); + var intersection = d3_geo_cartesianCross(meridianNormal, arc); + d3_geo_cartesianNormalize(intersection); + var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); + if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { + winding += antimeridian ^ dλ >= 0 ? 1 : -1; + } + } + if (!j++) break; + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; + } + } + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ winding & 1; + } function d3_geo_clipCircle(radius) { var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]); @@ -3114,18 +3688,15 @@ d3 = function() { for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) { b = v[j]; if (a[1] <= y) { - if (b[1] > y && isLeft(a, b, p) > 0) ++wn; + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; } else { - if (b[1] <= y && isLeft(a, b, p) < 0) --wn; + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; } a = b; } } return wn !== 0; } - function isLeft(a, b, c) { - return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); - } function interpolate(from, to, direction, listener) { var a = 0, a1 = 0; if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { @@ -3213,15 +3784,6 @@ d3 = function() { return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; } } - function d3_geo_compose(a, b) { - function compose(x, y) { - return x = a(x, y), b(x[0], x[1]); - } - if (a.invert && b.invert) compose.invert = function(x, y) { - return x = b.invert(x, y), x && a.invert(x[0], x[1]); - }; - return compose; - } function d3_geo_conic(projectAt) { var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); p.parallels = function(_) { @@ -3493,7 +4055,7 @@ d3 = function() { result: d3_noop }; function point(x, y) { - context.moveTo(x, y); + context.moveTo(x + pointRadius, y); context.arc(x, y, pointRadius, 0, τ); } function pointLineStart(x, y) { @@ -4084,7 +4646,12 @@ d3 = function() { }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; if (!n) return d3_geo_mercator; function forward(λ, φ) { - var ρ = abs(abs(φ) - halfπ) < ε ? 0 : F / Math.pow(t(φ), n); + if (F > 0) { + if (φ < -halfπ + ε) φ = -halfπ + ε; + } else { + if (φ > halfπ - ε) φ = halfπ - ε; + } + var ρ = F / Math.pow(t(φ), n); return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; } forward.invert = function(x, y) { @@ -4166,14 +4733,21 @@ d3 = function() { return d3_geo_projection(d3_geo_stereographic); }).raw = d3_geo_stereographic; function d3_geo_transverseMercator(λ, φ) { - var B = Math.cos(φ) * Math.sin(λ); - return [ Math.log((1 + B) / (1 - B)) / 2, Math.atan2(Math.tan(φ), Math.cos(λ)) ]; + return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ]; } d3_geo_transverseMercator.invert = function(x, y) { - return [ Math.atan2(d3_sinh(x), Math.cos(y)), d3_asin(Math.sin(y) / d3_cosh(x)) ]; + return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ]; }; (d3.geo.transverseMercator = function() { - return d3_geo_mercatorProjection(d3_geo_transverseMercator); + var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate; + projection.center = function(_) { + return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]); + }; + projection.rotate = function(_) { + return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), + [ _[0], _[1], _[2] - 90 ]); + }; + return rotate([ 0, 0, 90 ]); }).raw = d3_geo_transverseMercator; d3.geom = {}; function d3_geom_pointX(d) { @@ -4187,65 +4761,17 @@ d3 = function() { if (arguments.length) return hull(vertices); function hull(data) { if (data.length < 3) return []; - var fx = d3_functor(x), fy = d3_functor(y), n = data.length, vertices, plen = n - 1, points = [], stack = [], d, i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; - if (fx === d3_geom_pointX && y === d3_geom_pointY) vertices = data; else for (i = 0, - vertices = []; i < n; ++i) { - vertices.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]); + var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = []; + for (i = 0; i < n; i++) { + points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]); } - for (i = 1; i < n; ++i) { - if (vertices[i][1] < vertices[h][1] || vertices[i][1] == vertices[h][1] && vertices[i][0] < vertices[h][0]) h = i; - } - for (i = 0; i < n; ++i) { - if (i === h) continue; - y1 = vertices[i][1] - vertices[h][1]; - x1 = vertices[i][0] - vertices[h][0]; - points.push({ - angle: Math.atan2(y1, x1), - index: i - }); - } - points.sort(function(a, b) { - return a.angle - b.angle; - }); - a = points[0].angle; - v = points[0].index; - u = 0; - for (i = 1; i < plen; ++i) { - j = points[i].index; - if (a == points[i].angle) { - x1 = vertices[v][0] - vertices[h][0]; - y1 = vertices[v][1] - vertices[h][1]; - x2 = vertices[j][0] - vertices[h][0]; - y2 = vertices[j][1] - vertices[h][1]; - if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) { - points[i].index = -1; - continue; - } else { - points[u].index = -1; - } - } - a = points[i].angle; - u = i; - v = j; - } - stack.push(h); - for (i = 0, j = 0; i < 2; ++j) { - if (points[j].index > -1) { - stack.push(points[j].index); - i++; - } - } - sp = stack.length; - for (;j < plen; ++j) { - if (points[j].index < 0) continue; - while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) { - --sp; - } - stack[sp++] = points[j].index; - } - var poly = []; - for (i = sp - 1; i >= 0; --i) poly.push(data[stack[i]]); - return poly; + points.sort(d3_geom_hullOrder); + for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]); + var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints); + var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = []; + for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); + for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); + return polygon; } hull.x = function(_) { return arguments.length ? (x = _, hull) : x; @@ -4255,18 +4781,16 @@ d3 = function() { }; return hull; }; - function d3_geom_hullCCW(i1, i2, i3, v) { - var t, a, b, c, d, e, f; - t = v[i1]; - a = t[0]; - b = t[1]; - t = v[i2]; - c = t[0]; - d = t[1]; - t = v[i3]; - e = t[0]; - f = t[1]; - return (f - b) * (c - a) - (d - b) * (e - a) > 0; + function d3_geom_hullUpper(points) { + var n = points.length, hull = [ 0, 1 ], hs = 2; + for (var i = 2; i < n; i++) { + while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs; + hull[hs++] = i; + } + return hull.slice(0, hs); + } + function d3_geom_hullOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; } d3.geom.polygon = function(coordinates) { d3_subclass(coordinates, d3_geom_polygonPrototype); @@ -5039,11 +5563,11 @@ d3 = function() { } } function insertChild(n, d, x, y, x1, y1, x2, y2) { - var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = x >= sx, bottom = y >= sy, i = (bottom << 1) + right; + var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right; n.leaf = false; n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); - if (right) x1 = sx; else x2 = sx; - if (bottom) y1 = sy; else y2 = sy; + if (right) x1 = xm; else x2 = xm; + if (below) y1 = ym; else y2 = ym; insert(n, d, x, y, x1, y1, x2, y2); } var root = d3_geom_quadtreeNode(); @@ -5053,6 +5577,9 @@ d3 = function() { root.visit = function(f) { d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); }; + root.find = function(point) { + return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_); + }; i = -1; if (x1 == null) { while (++i < n) { @@ -5106,6 +5633,42 @@ d3 = function() { if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); } } + function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) { + var minDistance2 = Infinity, closestPoint; + (function find(node, x1, y1, x2, y2) { + if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; + if (point = node.point) { + var point, dx = x - point[0], dy = y - point[1], distance2 = dx * dx + dy * dy; + if (distance2 < minDistance2) { + var distance = Math.sqrt(minDistance2 = distance2); + x0 = x - distance, y0 = y - distance; + x3 = x + distance, y3 = y + distance; + closestPoint = point; + } + } + var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym; + for (var i = below << 1 | right, j = i + 4; i < j; ++i) { + if (node = children[i & 3]) switch (i & 3) { + case 0: + find(node, x1, y1, xm, ym); + break; + + case 1: + find(node, xm, y1, x2, ym); + break; + + case 2: + find(node, x1, ym, xm, y2); + break; + + case 3: + find(node, xm, ym, x2, y2); + break; + } + } + })(root, x0, y0, x3, y3); + return closestPoint; + } d3.interpolateRgb = d3_interpolateRgb; function d3_interpolateRgb(a, b) { a = d3.rgb(a); @@ -5137,78 +5700,45 @@ d3 = function() { } d3.interpolateNumber = d3_interpolateNumber; function d3_interpolateNumber(a, b) { - b -= a = +a; + a = +a, b = +b; return function(t) { - return a + b * t; + return a * (1 - t) + b * t; }; } d3.interpolateString = d3_interpolateString; function d3_interpolateString(a, b) { - var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o; + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = []; a = a + "", b = b + ""; - d3_interpolate_number.lastIndex = 0; - for (i = 0; m = d3_interpolate_number.exec(b); ++i) { - if (m.index) s.push(b.substring(s0, s1 = m.index)); - q.push({ - i: s.length, - x: m[0] - }); - s.push(null); - s0 = d3_interpolate_number.lastIndex; - } - if (s0 < b.length) s.push(b.substring(s0)); - for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { - o = q[i]; - if (o.x == m[0]) { - if (o.i) { - if (s[o.i + 1] == null) { - s[o.i - 1] += o.x; - s.splice(o.i, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } else { - s[o.i - 1] += o.x + s[o.i + 1]; - s.splice(o.i, 2); - for (j = i + 1; j < n; ++j) q[j].i -= 2; - } - } else { - if (s[o.i + 1] == null) { - s[o.i] = o.x; - } else { - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } - } - q.splice(i, 1); - n--; - i--; - } else { - o.x = d3_interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); + while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; else s[++i] = bs; } - } - while (i < n) { - o = q.pop(); - if (s[o.i + 1] == null) { - s[o.i] = o.x; + if ((am = am[0]) === (bm = bm[0])) { + if (s[i]) s[i] += bm; else s[++i] = bm; } else { - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); + s[++i] = null; + q.push({ + i: i, + x: d3_interpolateNumber(am, bm) + }); } - n--; + bi = d3_interpolate_numberB.lastIndex; } - if (s.length === 1) { - return s[0] == null ? (o = q[0].x, function(t) { - return o(t) + ""; - }) : function() { - return b; - }; + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; else s[++i] = bs; } - return function(t) { - for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); + return s.length < 2 ? q[0] ? (b = q[0].x, function(t) { + return b(t) + ""; + }) : function() { + return b; + } : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); return s.join(""); - }; + }); } - var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; + var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g"); d3.interpolate = d3_interpolate; function d3_interpolate(a, b) { var i = d3.interpolators.length, f; @@ -5217,7 +5747,7 @@ d3 = function() { } d3.interpolators = [ function(a, b) { var t = typeof b; - return (t === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_Color ? d3_interpolateRgb : t === "object" ? Array.isArray(b) ? d3_interpolateArray : d3_interpolateObject : d3_interpolateNumber)(a, b); + return (t === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b); } ]; d3.interpolateArray = d3_interpolateArray; function d3_interpolateArray(a, b) { @@ -5266,7 +5796,7 @@ d3 = function() { } }); d3.ease = function(name) { - var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; + var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in"; t = d3_ease.get(t) || d3_ease_default; m = d3_ease_mode.get(m) || d3_identity; return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); @@ -5471,15 +6001,15 @@ d3 = function() { }; } function d3_uninterpolateNumber(a, b) { - b = b - (a = +a) ? 1 / (b - a) : 0; + b = (b -= a = +a) || 1 / b; return function(x) { - return (x - a) * b; + return (x - a) / b; }; } function d3_uninterpolateClamp(a, b) { - b = b - (a = +a) ? 1 / (b - a) : 0; + b = (b -= a = +a) || 1 / b; return function(x) { - return Math.max(0, Math.min(1, (x - a) * b)); + return Math.max(0, Math.min(1, (x - a) / b)); }; } d3.layout = {}; @@ -5637,19 +6167,21 @@ d3 = function() { return chord; }; d3.layout.force = function() { - var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, nodes = [], links = [], distances, strengths, charges; + var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges; function repulse(node) { return function(quad, x1, _, x2) { if (quad.point !== node) { - var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy); - if ((x2 - x1) * dn < theta) { - var k = quad.charge * dn * dn; - node.px -= dx * k; - node.py -= dy * k; + var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy; + if (dw * dw / theta2 < dn) { + if (dn < chargeDistance2) { + var k = quad.charge / dn; + node.px -= dx * k; + node.py -= dy * k; + } return true; } - if (quad.point && isFinite(dn)) { - var k = quad.pointCharge * dn * dn; + if (quad.point && dn && dn < chargeDistance2) { + var k = quad.pointCharge / dn; node.px -= dx * k; node.py -= dy * k; } @@ -5753,14 +6285,19 @@ d3 = function() { charge = typeof x === "function" ? x : +x; return force; }; + force.chargeDistance = function(x) { + if (!arguments.length) return Math.sqrt(chargeDistance2); + chargeDistance2 = x * x; + return force; + }; force.gravity = function(x) { if (!arguments.length) return gravity; gravity = +x; return force; }; force.theta = function(x) { - if (!arguments.length) return theta; - theta = +x; + if (!arguments.length) return Math.sqrt(theta2); + theta2 = x * x; return force; }; force.alpha = function(x) { @@ -5878,44 +6415,33 @@ d3 = function() { quad.cx = cx / quad.charge; quad.cy = cy / quad.charge; } - var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1; + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity; d3.layout.hierarchy = function() { var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; - function recurse(node, depth, nodes) { - var childs = children.call(hierarchy, node, depth); - node.depth = depth; - nodes.push(node); - if (childs && (n = childs.length)) { - var i = -1, n, c = node.children = new Array(n), v = 0, j = depth + 1, d; - while (++i < n) { - d = c[i] = recurse(childs[i], j, nodes); - d.parent = node; - v += d.value; - } - if (sort) c.sort(sort); - if (value) node.value = v; - } else { - delete node.children; - if (value) { - node.value = +value.call(hierarchy, node, depth) || 0; + function hierarchy(root) { + var stack = [ root ], nodes = [], node; + root.depth = 0; + while ((node = stack.pop()) != null) { + nodes.push(node); + if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) { + var n, childs, child; + while (--n >= 0) { + stack.push(child = childs[n]); + child.parent = node; + child.depth = node.depth + 1; + } + if (value) node.value = 0; + node.children = childs; + } else { + if (value) node.value = +value.call(hierarchy, node, node.depth) || 0; + delete node.children; } } - return node; - } - function revalue(node, depth) { - var children = node.children, v = 0; - if (children && (n = children.length)) { - var i = -1, n, j = depth + 1; - while (++i < n) v += revalue(children[i], j); - } else if (value) { - v = +value.call(hierarchy, node, depth) || 0; - } - if (value) node.value = v; - return v; - } - function hierarchy(d) { - var nodes = []; - recurse(d, 0, nodes); + d3_layout_hierarchyVisitAfter(root, function(node) { + var childs, parent; + if (sort && (childs = node.children)) childs.sort(sort); + if (value && (parent = node.parent)) parent.value += node.value; + }); return nodes; } hierarchy.sort = function(x) { @@ -5934,7 +6460,16 @@ d3 = function() { return hierarchy; }; hierarchy.revalue = function(root) { - revalue(root, 0); + if (value) { + d3_layout_hierarchyVisitBefore(root, function(node) { + if (node.children) node.value = 0; + }); + d3_layout_hierarchyVisitAfter(root, function(node) { + var parent; + if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; + if (parent = node.parent) parent.value += node.value; + }); + } return root; }; return hierarchy; @@ -5945,6 +6480,29 @@ d3 = function() { object.links = d3_layout_hierarchyLinks; return object; } + function d3_layout_hierarchyVisitBefore(node, callback) { + var nodes = [ node ]; + while ((node = nodes.pop()) != null) { + callback(node); + if ((children = node.children) && (n = children.length)) { + var n, children; + while (--n >= 0) nodes.push(children[n]); + } + } + } + function d3_layout_hierarchyVisitAfter(node, callback) { + var nodes = [ node ], nodes2 = []; + while ((node = nodes.pop()) != null) { + nodes2.push(node); + if ((children = node.children) && (n = children.length)) { + var i = -1, n, children; + while (++i < n) nodes.push(children[i]); + } + } + while ((node = nodes2.pop()) != null) { + callback(node); + } + } function d3_layout_hierarchyChildren(d) { return d.children; } @@ -6002,49 +6560,50 @@ d3 = function() { return d3_layout_hierarchyRebind(partition, hierarchy); }; d3.layout.pie = function() { - var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ; + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0; function pie(data) { - var values = data.map(function(d, i) { + var n = data.length, values = data.map(function(d, i) { return +value.call(pie, d, i); - }); - var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle); - var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a) / d3.sum(values); - var index = d3.range(data.length); + }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), k = (da - n * pa) / d3.sum(values), index = d3.range(n), arcs = [], v; if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { return values[j] - values[i]; } : function(i, j) { return sort(data[i], data[j]); }); - var arcs = []; index.forEach(function(i) { - var d; arcs[i] = { data: data[i], - value: d = values[i], + value: v = values[i], startAngle: a, - endAngle: a += d * k + endAngle: a += v * k + pa, + padAngle: p }; }); return arcs; } - pie.value = function(x) { + pie.value = function(_) { if (!arguments.length) return value; - value = x; + value = _; return pie; }; - pie.sort = function(x) { + pie.sort = function(_) { if (!arguments.length) return sort; - sort = x; + sort = _; return pie; }; - pie.startAngle = function(x) { + pie.startAngle = function(_) { if (!arguments.length) return startAngle; - startAngle = x; + startAngle = _; return pie; }; - pie.endAngle = function(x) { + pie.endAngle = function(_) { if (!arguments.length) return endAngle; - endAngle = x; + endAngle = _; + return pie; + }; + pie.padAngle = function(_) { + if (!arguments.length) return padAngle; + padAngle = _; return pie; }; return pie; @@ -6053,6 +6612,7 @@ d3 = function() { d3.layout.stack = function() { var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; function stack(data, index) { + if (!(n = data.length)) return data; var series = data.map(function(d, i) { return values.call(stack, d, i); }); @@ -6065,7 +6625,7 @@ d3 = function() { series = d3.permute(series, orders); points = d3.permute(points, orders); var offsets = offset.call(stack, points, index); - var n = series.length, m = series[0].length, i, j, o; + var m = series[0].length, n, i, j, o; for (j = 0; j < m; ++j) { out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); for (i = 1; i < n; ++i) { @@ -6260,185 +6820,6 @@ d3 = function() { function d3_layout_histogramRange(values) { return [ d3.min(values), d3.max(values) ]; } - d3.layout.tree = function() { - var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; - function tree(d, i) { - var nodes = hierarchy.call(this, d, i), root = nodes[0]; - function firstWalk(node, previousSibling) { - var children = node.children, layout = node._tree; - if (children && (n = children.length)) { - var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; - while (++i < n) { - child = children[i]; - firstWalk(child, previousChild); - ancestor = apportion(child, previousChild, ancestor); - previousChild = child; - } - d3_layout_treeShift(node); - var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); - layout.mod = layout.prelim - midpoint; - } else { - layout.prelim = midpoint; - } - } else { - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); - } - } - } - function secondWalk(node, x) { - node.x = node._tree.prelim + x; - var children = node.children; - if (children && (n = children.length)) { - var i = -1, n; - x += node._tree.mod; - while (++i < n) { - secondWalk(children[i], x); - } - } - } - function apportion(node, previousSibling, ancestor) { - if (previousSibling) { - var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; - while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { - vom = d3_layout_treeLeft(vom); - vop = d3_layout_treeRight(vop); - vop._tree.ancestor = node; - shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); - if (shift > 0) { - d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); - sip += shift; - sop += shift; - } - sim += vim._tree.mod; - sip += vip._tree.mod; - som += vom._tree.mod; - sop += vop._tree.mod; - } - if (vim && !d3_layout_treeRight(vop)) { - vop._tree.thread = vim; - vop._tree.mod += sim - sop; - } - if (vip && !d3_layout_treeLeft(vom)) { - vom._tree.thread = vip; - vom._tree.mod += sip - som; - ancestor = node; - } - } - return ancestor; - } - d3_layout_treeVisitAfter(root, function(node, previousSibling) { - node._tree = { - ancestor: node, - prelim: 0, - mod: 0, - change: 0, - shift: 0, - number: previousSibling ? previousSibling._tree.number + 1 : 0 - }; - }); - firstWalk(root); - secondWalk(root, -root._tree.prelim); - var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1; - d3_layout_treeVisitAfter(root, nodeSize ? function(node) { - node.x *= size[0]; - node.y = node.depth * size[1]; - delete node._tree; - } : function(node) { - node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = node.depth / y1 * size[1]; - delete node._tree; - }); - return nodes; - } - tree.separation = function(x) { - if (!arguments.length) return separation; - separation = x; - return tree; - }; - tree.size = function(x) { - if (!arguments.length) return nodeSize ? null : size; - nodeSize = (size = x) == null; - return tree; - }; - tree.nodeSize = function(x) { - if (!arguments.length) return nodeSize ? size : null; - nodeSize = (size = x) != null; - return tree; - }; - return d3_layout_hierarchyRebind(tree, hierarchy); - }; - function d3_layout_treeSeparation(a, b) { - return a.parent == b.parent ? 1 : 2; - } - function d3_layout_treeLeft(node) { - var children = node.children; - return children && children.length ? children[0] : node._tree.thread; - } - function d3_layout_treeRight(node) { - var children = node.children, n; - return children && (n = children.length) ? children[n - 1] : node._tree.thread; - } - function d3_layout_treeSearch(node, compare) { - var children = node.children; - if (children && (n = children.length)) { - var child, n, i = -1; - while (++i < n) { - if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { - node = child; - } - } - } - return node; - } - function d3_layout_treeRightmost(a, b) { - return a.x - b.x; - } - function d3_layout_treeLeftmost(a, b) { - return b.x - a.x; - } - function d3_layout_treeDeepest(a, b) { - return a.depth - b.depth; - } - function d3_layout_treeVisitAfter(node, callback) { - function visit(node, previousSibling) { - var children = node.children; - if (children && (n = children.length)) { - var child, previousChild = null, i = -1, n; - while (++i < n) { - child = children[i]; - visit(child, previousChild); - previousChild = child; - } - } - callback(node, previousSibling); - } - visit(node, null); - } - function d3_layout_treeShift(node) { - var shift = 0, change = 0, children = node.children, i = children.length, child; - while (--i >= 0) { - child = children[i]._tree; - child.prelim += shift; - child.mod += shift; - shift += child.shift + (change += child.change); - } - } - function d3_layout_treeMove(ancestor, node, shift) { - ancestor = ancestor._tree; - node = node._tree; - var change = shift / (node.number - ancestor.number); - ancestor.change += change; - node.change -= change; - node.shift += shift; - node.prelim += shift; - node.mod += shift; - } - function d3_layout_treeAncestor(vim, node, ancestor) { - return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; - } d3.layout.pack = function() { var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius; function pack(d, i) { @@ -6446,17 +6827,17 @@ d3 = function() { return radius; }; root.x = root.y = 0; - d3_layout_treeVisitAfter(root, function(d) { + d3_layout_hierarchyVisitAfter(root, function(d) { d.r = +r(d.value); }); - d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); if (padding) { var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2; - d3_layout_treeVisitAfter(root, function(d) { + d3_layout_hierarchyVisitAfter(root, function(d) { d.r += dr; }); - d3_layout_treeVisitAfter(root, d3_layout_packSiblings); - d3_layout_treeVisitAfter(root, function(d) { + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, function(d) { d.r -= dr; }); } @@ -6593,11 +6974,158 @@ d3 = function() { c.y = a.y; } } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0); + d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z; + d3_layout_hierarchyVisitBefore(root1, secondWalk); + if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else { + var left = root0, right = root0, bottom = root0; + d3_layout_hierarchyVisitBefore(root0, function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1); + d3_layout_hierarchyVisitBefore(root0, function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + return nodes; + } + function wrapTree(root0) { + var root1 = { + A: null, + children: [ root0 ] + }, queue = [ root1 ], node1; + while ((node1 = queue.pop()) != null) { + for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) { + queue.push((children[i] = child = { + _: children[i], + parent: node1, + children: (child = children[i].children) && child.slice() || [], + A: null, + a: null, + z: 0, + m: 0, + c: 0, + s: 0, + t: null, + i: i + }).a = child); + } + } + return root1.children[0]; + } + function firstWalk(v) { + var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; + if (children.length) { + d3_layout_treeShift(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + function apportion(v, w, ancestor) { + if (w) { + var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !d3_layout_treeRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + function sizeNode(node) { + node.x *= size[0]; + node.y = node.depth * size[1]; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null ? sizeNode : null; + return tree; + }; + tree.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) == null ? null : sizeNode; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(v) { + var children = v.children; + return children.length ? children[0] : v.t; + } + function d3_layout_treeRight(v) { + var children = v.children, n; + return (n = children.length) ? children[n - 1] : v.t; + } + function d3_layout_treeMove(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; + } + function d3_layout_treeShift(v) { + var shift = 0, change = 0, children = v.children, i = children.length, w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } + } + function d3_layout_treeAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; + } d3.layout.cluster = function() { var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; function cluster(d, i) { var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; - d3_layout_treeVisitAfter(root, function(node) { + d3_layout_hierarchyVisitAfter(root, function(node) { var children = node.children; if (children && children.length) { node.x = d3_layout_clusterX(children); @@ -6609,7 +7137,7 @@ d3 = function() { } }); var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; - d3_layout_treeVisitAfter(root, nodeSize ? function(node) { + d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) { node.x = (node.x - root.x) * size[0]; node.y = (root.y - node.y) * size[1]; } : function(node) { @@ -6847,10 +7375,16 @@ d3 = function() { return Math.exp(random()); }; }, + bates: function(m) { + var random = d3.random.irwinHall(m); + return function() { + return random() / m; + }; + }, irwinHall: function(m) { return function() { for (var s = 0, j = 0; j < m; j++) s += Math.random(); - return s / m; + return s; }; } }; @@ -6982,9 +7516,24 @@ d3 = function() { } function d3_scale_linearTickFormat(domain, m, format) { var range = d3_scale_linearTickRange(domain, m); - return d3.format(format ? format.replace(d3_format_re, function(a, b, c, d, e, f, g, h, i, j) { - return [ b, c, d, e, f, g, h, i || "." + d3_scale_linearFormatPrecision(j, range), j ].join(""); - }) : ",." + d3_scale_linearPrecision(range[2]) + "f"); + if (format) { + var match = d3_format_re.exec(format); + match.shift(); + if (match[8] === "s") { + var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1]))); + if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2])); + match[8] = "f"; + format = d3.format(match.join("")); + return function(d) { + return format(prefix.scale(d)) + prefix.symbol; + }; + } + if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range); + format = match.join(""); + } else { + format = ",." + d3_scale_linearPrecision(range[2]) + "f"; + } + return d3.format(format); } var d3_scale_linearFormatSignificant = { s: 1, @@ -6998,7 +7547,7 @@ d3 = function() { } function d3_scale_linearFormatPrecision(type, range) { var p = d3_scale_linearPrecision(range[2]); - return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(Math.abs(range[0]), Math.abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; + return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; } d3.scale.log = function() { return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]); @@ -7126,7 +7675,7 @@ d3 = function() { function d3_scale_ordinal(domain, ranger) { var index, range, rangeBand; function scale(x) { - return range[((index.get(x) || ranger.t === "range" && index.set(x, domain.push(x))) - 1) % range.length]; + return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length]; } function steps(start, step) { return d3.range(domain.length).map(function(i) { @@ -7153,8 +7702,9 @@ d3 = function() { }; scale.rangePoints = function(x, padding) { if (arguments.length < 2) padding = 0; - var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding); - range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, + 0) : (stop - start) / (domain.length - 1 + padding); + range = steps(start + step * padding / 2, step); rangeBand = 0; ranger = { t: "rangePoints", @@ -7162,6 +7712,18 @@ d3 = function() { }; return scale; }; + scale.rangeRoundPoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), + 0) : (stop - start) / (domain.length - 1 + padding) | 0; + range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step); + rangeBand = 0; + ranger = { + t: "rangeRoundPoints", + a: arguments + }; + return scale; + }; scale.rangeBands = function(x, padding, outerPadding) { if (arguments.length < 2) padding = 0; if (arguments.length < 3) outerPadding = padding; @@ -7178,8 +7740,8 @@ d3 = function() { scale.rangeRoundBands = function(x, padding, outerPadding) { if (arguments.length < 2) padding = 0; if (arguments.length < 3) outerPadding = padding; - var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step; - range = steps(start + Math.round(error / 2), step); + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); if (reverse) range.reverse(); rangeBand = Math.round(step * (1 - padding)); ranger = { @@ -7231,9 +7793,7 @@ d3 = function() { } scale.domain = function(x) { if (!arguments.length) return domain; - domain = x.filter(function(d) { - return !isNaN(d); - }).sort(d3.ascending); + domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending); return rescale(); }; scale.range = function(x) { @@ -7338,12 +7898,86 @@ d3 = function() { return identity; } d3.svg = {}; + function d3_zero() { + return 0; + } d3.svg.arc = function() { - var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle; function arc() { - var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, - a0 = a1, a1 = da), a1 - a0), df = da < π ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1); - return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z"; + var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1; + if (r1 < r0) rc = r1, r1 = r0, r0 = rc; + if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; + var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = []; + if (ap = (+padAngle.apply(this, arguments) || 0) / 2) { + rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments); + if (!cw) p1 *= -1; + if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap)); + if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap)); + } + if (r1) { + x0 = r1 * Math.cos(a0 + p1); + y0 = r1 * Math.sin(a0 + p1); + x1 = r1 * Math.cos(a1 - p1); + y1 = r1 * Math.sin(a1 - p1); + var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1; + if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) { + var h1 = (a0 + a1) / 2; + x0 = r1 * Math.cos(h1); + y0 = r1 * Math.sin(h1); + x1 = y1 = null; + } + } else { + x0 = y0 = 0; + } + if (r0) { + x2 = r0 * Math.cos(a1 - p0); + y2 = r0 * Math.sin(a1 - p0); + x3 = r0 * Math.cos(a0 + p0); + y3 = r0 * Math.sin(a0 + p0); + var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1; + if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) { + var h0 = (a0 + a1) / 2; + x2 = r0 * Math.cos(h0); + y2 = r0 * Math.sin(h0); + x3 = y3 = null; + } + } else { + x2 = y2 = 0; + } + if ((rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) { + cr = r0 < r1 ^ cw ? 0 : 1; + var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + if (x1 != null) { + var rc1 = Math.min(rc, (r1 - lc) / (kc + 1)), t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw); + if (rc === rc1) { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]); + } else { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]); + } + } else { + path.push("M", x0, ",", y0); + } + if (x3 != null) { + var rc0 = Math.min(rc, (r0 - lc) / (kc - 1)), t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw); + if (rc === rc0) { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } else { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } + } else { + path.push("L", x2, ",", y2); + } + } else { + path.push("M", x0, ",", y0); + if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1); + path.push("L", x2, ",", y2); + if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3); + } + path.push("Z"); + return path.join(""); + } + function circleSegment(r1, cw) { + return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1; } arc.innerRadius = function(v) { if (!arguments.length) return innerRadius; @@ -7355,6 +7989,16 @@ d3 = function() { outerRadius = d3_functor(v); return arc; }; + arc.cornerRadius = function(v) { + if (!arguments.length) return cornerRadius; + cornerRadius = d3_functor(v); + return arc; + }; + arc.padRadius = function(v) { + if (!arguments.length) return padRadius; + padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v); + return arc; + }; arc.startAngle = function(v) { if (!arguments.length) return startAngle; startAngle = d3_functor(v); @@ -7365,13 +8009,18 @@ d3 = function() { endAngle = d3_functor(v); return arc; }; + arc.padAngle = function(v) { + if (!arguments.length) return padAngle; + padAngle = d3_functor(v); + return arc; + }; arc.centroid = function() { - var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ; return [ Math.cos(a) * r, Math.sin(a) * r ]; }; return arc; }; - var d3_svg_arcOffset = -halfπ, d3_svg_arcMax = τ - ε; + var d3_svg_arcAuto = "auto"; function d3_svg_arcInnerRadius(d) { return d.innerRadius; } @@ -7384,6 +8033,17 @@ d3 = function() { function d3_svg_arcEndAngle(d) { return d.endAngle; } + function d3_svg_arcPadAngle(d) { + return d && d.padAngle; + } + function d3_svg_arcSweep(x0, y0, x1, y1) { + return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1; + } + function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) { + var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(r * r * d2 - D * D), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ]; + } function d3_svg_line(projection) { var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; function line(data) { @@ -7474,7 +8134,7 @@ d3 = function() { return path.join(""); } function d3_svg_lineCardinalOpen(points, tension) { - return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension)); + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); } function d3_svg_lineCardinalClosed(points, tension) { return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), @@ -7644,7 +8304,7 @@ d3 = function() { while (++i < n) { point = points[i]; r = point[0]; - a = point[1] + d3_svg_arcOffset; + a = point[1] - halfπ; point[0] = r * Math.cos(a); point[1] = r * Math.sin(a); } @@ -7745,7 +8405,7 @@ d3 = function() { return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; } function subgroup(self, f, d, i) { - var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ; return { r: r, a0: a0, @@ -7835,7 +8495,7 @@ d3 = function() { }; function d3_svg_diagonalRadialProjection(projection) { return function() { - var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset; + var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ; return [ r * Math.cos(a), r * Math.sin(a) ]; }; } @@ -7891,8 +8551,39 @@ d3 = function() { }); d3.svg.symbolTypes = d3_svg_symbols.keys(); var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); - function d3_transition(groups, id) { + d3_selectionPrototype.transition = function(name) { + var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || { + time: Date.now(), + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, ns, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id); + }; + d3_selectionPrototype.interrupt = function(name) { + return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name))); + }; + var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace()); + function d3_selection_interruptNS(ns) { + return function() { + var lock, active; + if ((lock = this[ns]) && (active = lock[lock.active])) { + if (--lock.count) delete lock[lock.active]; else delete this[ns]; + lock.active += .5; + active.event && active.event.interrupt.call(this, this.__data__, active.index); + } + }; + } + function d3_transition(groups, ns, id) { d3_subclass(groups, d3_transitionPrototype); + groups.namespace = ns; groups.id = id; return groups; } @@ -7901,44 +8592,44 @@ d3 = function() { d3_transitionPrototype.empty = d3_selectionPrototype.empty; d3_transitionPrototype.node = d3_selectionPrototype.node; d3_transitionPrototype.size = d3_selectionPrototype.size; - d3.transition = function(selection) { - return arguments.length ? d3_transitionInheritId ? selection.transition() : selection : d3_selectionRoot.transition(); + d3.transition = function(selection, name) { + return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3_selectionRoot.transition(selection); }; d3.transition.prototype = d3_transitionPrototype; d3_transitionPrototype.select = function(selector) { - var id = this.id, subgroups = [], subgroup, subnode, node; + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node; selector = d3_selection_selector(selector); for (var j = -1, m = this.length; ++j < m; ) { subgroups.push(subgroup = []); for (var group = this[j], i = -1, n = group.length; ++i < n; ) { if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { if ("__data__" in node) subnode.__data__ = node.__data__; - d3_transitionNode(subnode, i, id, node.__transition__[id]); + d3_transitionNode(subnode, i, ns, id, node[ns][id]); subgroup.push(subnode); } else { subgroup.push(null); } } } - return d3_transition(subgroups, id); + return d3_transition(subgroups, ns, id); }; d3_transitionPrototype.selectAll = function(selector) { - var id = this.id, subgroups = [], subgroup, subnodes, node, subnode, transition; + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition; selector = d3_selection_selectorAll(selector); for (var j = -1, m = this.length; ++j < m; ) { for (var group = this[j], i = -1, n = group.length; ++i < n; ) { if (node = group[i]) { - transition = node.__transition__[id]; + transition = node[ns][id]; subnodes = selector.call(node, node.__data__, i, j); subgroups.push(subgroup = []); for (var k = -1, o = subnodes.length; ++k < o; ) { - if (subnode = subnodes[k]) d3_transitionNode(subnode, k, id, transition); + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition); subgroup.push(subnode); } } } } - return d3_transition(subgroups, id); + return d3_transition(subgroups, ns, id); }; d3_transitionPrototype.filter = function(filter) { var subgroups = [], subgroup, group, node; @@ -7951,23 +8642,23 @@ d3 = function() { } } } - return d3_transition(subgroups, this.id); + return d3_transition(subgroups, this.namespace, this.id); }; d3_transitionPrototype.tween = function(name, tween) { - var id = this.id; - if (arguments.length < 2) return this.node().__transition__[id].tween.get(name); + var id = this.id, ns = this.namespace; + if (arguments.length < 2) return this.node()[ns][id].tween.get(name); return d3_selection_each(this, tween == null ? function(node) { - node.__transition__[id].tween.remove(name); + node[ns][id].tween.remove(name); } : function(node) { - node.__transition__[id].tween.set(name, tween); + node[ns][id].tween.set(name, tween); }); }; function d3_transition_tween(groups, name, value, tween) { - var id = groups.id; + var id = groups.id, ns = groups.namespace; return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { - node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j))); } : (value = tween(value), function(node) { - node.__transition__[id].tween.set(name, value); + node[ns][id].tween.set(name, value); })); } d3_transitionPrototype.attr = function(nameNS, value) { @@ -8059,71 +8750,84 @@ d3 = function() { }; } d3_transitionPrototype.remove = function() { + var ns = this.namespace; return this.each("end.transition", function() { var p; - if (this.__transition__.count < 2 && (p = this.parentNode)) p.removeChild(this); + if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this); }); }; d3_transitionPrototype.ease = function(value) { - var id = this.id; - if (arguments.length < 1) return this.node().__transition__[id].ease; + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].ease; if (typeof value !== "function") value = d3.ease.apply(d3, arguments); return d3_selection_each(this, function(node) { - node.__transition__[id].ease = value; + node[ns][id].ease = value; }); }; d3_transitionPrototype.delay = function(value) { - var id = this.id; + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].delay; return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { - node.__transition__[id].delay = +value.call(node, node.__data__, i, j); + node[ns][id].delay = +value.call(node, node.__data__, i, j); } : (value = +value, function(node) { - node.__transition__[id].delay = value; + node[ns][id].delay = value; })); }; d3_transitionPrototype.duration = function(value) { - var id = this.id; + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].duration; return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { - node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j)); + node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j)); } : (value = Math.max(1, value), function(node) { - node.__transition__[id].duration = value; + node[ns][id].duration = value; })); }; d3_transitionPrototype.each = function(type, listener) { - var id = this.id; + var id = this.id, ns = this.namespace; if (arguments.length < 2) { var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; - d3_transitionInheritId = id; - d3_selection_each(this, function(node, i, j) { - d3_transitionInherit = node.__transition__[id]; - type.call(node, node.__data__, i, j); - }); - d3_transitionInherit = inherit; - d3_transitionInheritId = inheritId; + try { + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node[ns][id]; + type.call(node, node.__data__, i, j); + }); + } finally { + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } } else { d3_selection_each(this, function(node) { - var transition = node.__transition__[id]; - (transition.event || (transition.event = d3.dispatch("start", "end"))).on(type, listener); + var transition = node[ns][id]; + (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener); }); } return this; }; d3_transitionPrototype.transition = function() { - var id0 = this.id, id1 = ++d3_transitionId, subgroups = [], subgroup, group, node, transition; + var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition; for (var j = 0, m = this.length; j < m; j++) { subgroups.push(subgroup = []); for (var group = this[j], i = 0, n = group.length; i < n; i++) { if (node = group[i]) { - transition = Object.create(node.__transition__[id0]); - transition.delay += transition.duration; - d3_transitionNode(node, i, id1, transition); + transition = node[ns][id0]; + d3_transitionNode(node, i, ns, id1, { + time: transition.time, + ease: transition.ease, + delay: transition.delay + transition.duration, + duration: transition.duration + }); } subgroup.push(node); } } - return d3_transition(subgroups, id1); + return d3_transition(subgroups, ns, id1); }; - function d3_transitionNode(node, i, id, inherit) { - var lock = node.__transition__ || (node.__transition__ = { + function d3_transitionNamespace(name) { + return name == null ? "__transition__" : "__transition_" + name + "__"; + } + function d3_transitionNode(node, i, ns, id, inherit) { + var lock = node[ns] || (node[ns] = { active: 0, count: 0 }), transition = lock[id]; @@ -8132,43 +8836,53 @@ d3 = function() { transition = lock[id] = { tween: new d3_Map(), time: time, - ease: inherit.ease, delay: inherit.delay, - duration: inherit.duration + duration: inherit.duration, + ease: inherit.ease, + index: i }; + inherit = null; ++lock.count; d3.timer(function(elapsed) { - var d = node.__data__, ease = transition.ease, delay = transition.delay, duration = transition.duration, timer = d3_timer_active, tweened = []; + var delay = transition.delay, duration, ease, timer = d3_timer_active, tweened = []; timer.t = delay + time; if (delay <= elapsed) return start(elapsed - delay); timer.c = start; function start(elapsed) { if (lock.active > id) return stop(); + var active = lock[lock.active]; + if (active) { + --lock.count; + delete lock[lock.active]; + active.event && active.event.interrupt.call(node, node.__data__, active.index); + } lock.active = id; - transition.event && transition.event.start.call(node, d, i); + transition.event && transition.event.start.call(node, node.__data__, i); transition.tween.forEach(function(key, value) { - if (value = value.call(node, d, i)) { + if (value = value.call(node, node.__data__, i)) { tweened.push(value); } }); + ease = transition.ease; + duration = transition.duration; d3.timer(function() { timer.c = tick(elapsed || 1) ? d3_true : tick; return 1; }, 0, time); } function tick(elapsed) { - if (lock.active !== id) return stop(); + if (lock.active !== id) return 1; var t = elapsed / duration, e = ease(t), n = tweened.length; while (n > 0) { tweened[--n].call(node, e); } if (t >= 1) { - transition.event && transition.event.end.call(node, d, i); + transition.event && transition.event.end.call(node, node.__data__, i); return stop(); } } function stop() { - if (--lock.count) delete lock[id]; else delete node.__transition__; + if (--lock.count) delete lock[id]; else delete node[ns]; return 1; } }, 0, time); @@ -8180,61 +8894,25 @@ d3 = function() { g.each(function() { var g = d3.select(this); var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy(); - var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; + var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform; var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), d3.transition(path)); tickEnter.append("line"); tickEnter.append("text"); - var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"); - switch (orient) { - case "bottom": - { - tickTransform = d3_svg_axisX; - lineEnter.attr("y2", innerTickSize); - textEnter.attr("y", Math.max(innerTickSize, 0) + tickPadding); - lineUpdate.attr("x2", 0).attr("y2", innerTickSize); - textUpdate.attr("x", 0).attr("y", Math.max(innerTickSize, 0) + tickPadding); - text.attr("dy", ".71em").style("text-anchor", "middle"); - pathUpdate.attr("d", "M" + range[0] + "," + outerTickSize + "V0H" + range[1] + "V" + outerTickSize); - break; - } - - case "top": - { - tickTransform = d3_svg_axisX; - lineEnter.attr("y2", -innerTickSize); - textEnter.attr("y", -(Math.max(innerTickSize, 0) + tickPadding)); - lineUpdate.attr("x2", 0).attr("y2", -innerTickSize); - textUpdate.attr("x", 0).attr("y", -(Math.max(innerTickSize, 0) + tickPadding)); - text.attr("dy", "0em").style("text-anchor", "middle"); - pathUpdate.attr("d", "M" + range[0] + "," + -outerTickSize + "V0H" + range[1] + "V" + -outerTickSize); - break; - } - - case "left": - { - tickTransform = d3_svg_axisY; - lineEnter.attr("x2", -innerTickSize); - textEnter.attr("x", -(Math.max(innerTickSize, 0) + tickPadding)); - lineUpdate.attr("x2", -innerTickSize).attr("y2", 0); - textUpdate.attr("x", -(Math.max(innerTickSize, 0) + tickPadding)).attr("y", 0); - text.attr("dy", ".32em").style("text-anchor", "end"); - pathUpdate.attr("d", "M" + -outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + -outerTickSize); - break; - } - - case "right": - { - tickTransform = d3_svg_axisY; - lineEnter.attr("x2", innerTickSize); - textEnter.attr("x", Math.max(innerTickSize, 0) + tickPadding); - lineUpdate.attr("x2", innerTickSize).attr("y2", 0); - textUpdate.attr("x", Math.max(innerTickSize, 0) + tickPadding).attr("y", 0); - text.attr("dy", ".32em").style("text-anchor", "start"); - pathUpdate.attr("d", "M" + outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + outerTickSize); - break; - } + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2; + if (orient === "bottom" || orient === "top") { + tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2"; + text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize); + } else { + tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2"; + text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start"); + pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize); } + lineEnter.attr(y2, sign * innerTickSize); + textEnter.attr(y1, sign * tickSpacing); + lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize); + textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing); if (scale1.rangeBand) { var x = scale1, dx = x.rangeBand() / 2; scale0 = scale1 = function(d) { @@ -8243,10 +8921,10 @@ d3 = function() { } else if (scale0.rangeBand) { scale0 = scale1; } else { - tickExit.call(tickTransform, scale1); + tickExit.call(tickTransform, scale1, scale0); } - tickEnter.call(tickTransform, scale0); - tickUpdate.call(tickTransform, scale1); + tickEnter.call(tickTransform, scale0, scale1); + tickUpdate.call(tickTransform, scale1, scale1); }); } axis.scale = function(x) { @@ -8307,14 +8985,16 @@ d3 = function() { bottom: 1, left: 1 }; - function d3_svg_axisX(selection, x) { + function d3_svg_axisX(selection, x0, x1) { selection.attr("transform", function(d) { - return "translate(" + x(d) + ",0)"; + var v0 = x0(d); + return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; }); } - function d3_svg_axisY(selection, y) { + function d3_svg_axisY(selection, y0, y1) { selection.attr("transform", function(d) { - return "translate(0," + y(d) + ")"; + var v0 = y0(d); + return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; }); } d3.svg.brush = function() { @@ -8607,491 +9287,8 @@ d3 = function() { sw: "nesw-resize" }; var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; - var d3_time = d3.time = {}, d3_date = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; - function d3_date_utc() { - this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); - } - d3_date_utc.prototype = { - getDate: function() { - return this._.getUTCDate(); - }, - getDay: function() { - return this._.getUTCDay(); - }, - getFullYear: function() { - return this._.getUTCFullYear(); - }, - getHours: function() { - return this._.getUTCHours(); - }, - getMilliseconds: function() { - return this._.getUTCMilliseconds(); - }, - getMinutes: function() { - return this._.getUTCMinutes(); - }, - getMonth: function() { - return this._.getUTCMonth(); - }, - getSeconds: function() { - return this._.getUTCSeconds(); - }, - getTime: function() { - return this._.getTime(); - }, - getTimezoneOffset: function() { - return 0; - }, - valueOf: function() { - return this._.valueOf(); - }, - setDate: function() { - d3_time_prototype.setUTCDate.apply(this._, arguments); - }, - setDay: function() { - d3_time_prototype.setUTCDay.apply(this._, arguments); - }, - setFullYear: function() { - d3_time_prototype.setUTCFullYear.apply(this._, arguments); - }, - setHours: function() { - d3_time_prototype.setUTCHours.apply(this._, arguments); - }, - setMilliseconds: function() { - d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); - }, - setMinutes: function() { - d3_time_prototype.setUTCMinutes.apply(this._, arguments); - }, - setMonth: function() { - d3_time_prototype.setUTCMonth.apply(this._, arguments); - }, - setSeconds: function() { - d3_time_prototype.setUTCSeconds.apply(this._, arguments); - }, - setTime: function() { - d3_time_prototype.setTime.apply(this._, arguments); - } - }; - var d3_time_prototype = Date.prototype; - var d3_time_formatDateTime = "%a %b %e %X %Y", d3_time_formatDate = "%m/%d/%Y", d3_time_formatTime = "%H:%M:%S"; - var d3_time_days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], d3_time_dayAbbreviations = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; - function d3_time_interval(local, step, number) { - function round(date) { - var d0 = local(date), d1 = offset(d0, 1); - return date - d0 < d1 - date ? d0 : d1; - } - function ceil(date) { - step(date = local(new d3_date(date - 1)), 1); - return date; - } - function offset(date, k) { - step(date = new d3_date(+date), k); - return date; - } - function range(t0, t1, dt) { - var time = ceil(t0), times = []; - if (dt > 1) { - while (time < t1) { - if (!(number(time) % dt)) times.push(new Date(+time)); - step(time, 1); - } - } else { - while (time < t1) times.push(new Date(+time)), step(time, 1); - } - return times; - } - function range_utc(t0, t1, dt) { - try { - d3_date = d3_date_utc; - var utc = new d3_date_utc(); - utc._ = t0; - return range(utc, t1, dt); - } finally { - d3_date = Date; - } - } - local.floor = local; - local.round = round; - local.ceil = ceil; - local.offset = offset; - local.range = range; - var utc = local.utc = d3_time_interval_utc(local); - utc.floor = utc; - utc.round = d3_time_interval_utc(round); - utc.ceil = d3_time_interval_utc(ceil); - utc.offset = d3_time_interval_utc(offset); - utc.range = range_utc; - return local; - } - function d3_time_interval_utc(method) { - return function(date, k) { - try { - d3_date = d3_date_utc; - var utc = new d3_date_utc(); - utc._ = date; - return method(utc, k)._; - } finally { - d3_date = Date; - } - }; - } - d3_time.year = d3_time_interval(function(date) { - date = d3_time.day(date); - date.setMonth(0, 1); - return date; - }, function(date, offset) { - date.setFullYear(date.getFullYear() + offset); - }, function(date) { - return date.getFullYear(); - }); - d3_time.years = d3_time.year.range; - d3_time.years.utc = d3_time.year.utc.range; - d3_time.day = d3_time_interval(function(date) { - var day = new d3_date(2e3, 0); - day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); - return day; - }, function(date, offset) { - date.setDate(date.getDate() + offset); - }, function(date) { - return date.getDate() - 1; - }); - d3_time.days = d3_time.day.range; - d3_time.days.utc = d3_time.day.utc.range; - d3_time.dayOfYear = function(date) { - var year = d3_time.year(date); - return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); - }; - d3_time_daySymbols.forEach(function(day, i) { - day = day.toLowerCase(); - i = 7 - i; - var interval = d3_time[day] = d3_time_interval(function(date) { - (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); - return date; - }, function(date, offset) { - date.setDate(date.getDate() + Math.floor(offset) * 7); - }, function(date) { - var day = d3_time.year(date).getDay(); - return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); - }); - d3_time[day + "s"] = interval.range; - d3_time[day + "s"].utc = interval.utc.range; - d3_time[day + "OfYear"] = function(date) { - var day = d3_time.year(date).getDay(); - return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); - }; - }); - d3_time.week = d3_time.sunday; - d3_time.weeks = d3_time.sunday.range; - d3_time.weeks.utc = d3_time.sunday.utc.range; - d3_time.weekOfYear = d3_time.sundayOfYear; - d3_time.format = d3_time_format; - function d3_time_format(template) { - var n = template.length; - function format(date) { - var string = [], i = -1, j = 0, c, p, f; - while (++i < n) { - if (template.charCodeAt(i) === 37) { - string.push(template.substring(j, i)); - if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); - if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); - string.push(c); - j = i + 1; - } - } - string.push(template.substring(j, i)); - return string.join(""); - } - format.parse = function(string) { - var d = { - y: 1900, - m: 0, - d: 1, - H: 0, - M: 0, - S: 0, - L: 0, - Z: null - }, i = d3_time_parse(d, template, string, 0); - if (i != string.length) return null; - if ("p" in d) d.H = d.H % 12 + d.p * 12; - var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); - if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("w" in d && ("W" in d || "U" in d)) { - date.setFullYear(d.y, 0, 1); - date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); - } else date.setFullYear(d.y, d.m, d.d); - date.setHours(d.H + Math.floor(d.Z / 100), d.M + d.Z % 100, d.S, d.L); - return localZ ? date._ : date; - }; - format.toString = function() { - return template; - }; - return format; - } - function d3_time_parse(date, template, string, j) { - var c, p, t, i = 0, n = template.length, m = string.length; - while (i < n) { - if (j >= m) return -1; - c = template.charCodeAt(i++); - if (c === 37) { - t = template.charAt(i++); - p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; - if (!p || (j = p(date, string, j)) < 0) return -1; - } else if (c != string.charCodeAt(j++)) { - return -1; - } - } - return j; - } - function d3_time_formatRe(names) { - return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); - } - function d3_time_formatLookup(names) { - var map = new d3_Map(), i = -1, n = names.length; - while (++i < n) map.set(names[i].toLowerCase(), i); - return map; - } - function d3_time_formatPad(value, fill, width) { - var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; - return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); - } - var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayLookup = d3_time_formatLookup(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_dayAbbrevLookup = d3_time_formatLookup(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations), d3_time_percentRe = /^%/; - var d3_time_formatPads = { - "-": "", - _: " ", - "0": "0" - }; - var d3_time_formats = { - a: function(d) { - return d3_time_dayAbbreviations[d.getDay()]; - }, - A: function(d) { - return d3_time_days[d.getDay()]; - }, - b: function(d) { - return d3_time_monthAbbreviations[d.getMonth()]; - }, - B: function(d) { - return d3_time_months[d.getMonth()]; - }, - c: d3_time_format(d3_time_formatDateTime), - d: function(d, p) { - return d3_time_formatPad(d.getDate(), p, 2); - }, - e: function(d, p) { - return d3_time_formatPad(d.getDate(), p, 2); - }, - H: function(d, p) { - return d3_time_formatPad(d.getHours(), p, 2); - }, - I: function(d, p) { - return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); - }, - j: function(d, p) { - return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); - }, - L: function(d, p) { - return d3_time_formatPad(d.getMilliseconds(), p, 3); - }, - m: function(d, p) { - return d3_time_formatPad(d.getMonth() + 1, p, 2); - }, - M: function(d, p) { - return d3_time_formatPad(d.getMinutes(), p, 2); - }, - p: function(d) { - return d.getHours() >= 12 ? "PM" : "AM"; - }, - S: function(d, p) { - return d3_time_formatPad(d.getSeconds(), p, 2); - }, - U: function(d, p) { - return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); - }, - w: function(d) { - return d.getDay(); - }, - W: function(d, p) { - return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); - }, - x: d3_time_format(d3_time_formatDate), - X: d3_time_format(d3_time_formatTime), - y: function(d, p) { - return d3_time_formatPad(d.getFullYear() % 100, p, 2); - }, - Y: function(d, p) { - return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); - }, - Z: d3_time_zone, - "%": function() { - return "%"; - } - }; - var d3_time_parsers = { - a: d3_time_parseWeekdayAbbrev, - A: d3_time_parseWeekday, - b: d3_time_parseMonthAbbrev, - B: d3_time_parseMonth, - c: d3_time_parseLocaleFull, - d: d3_time_parseDay, - e: d3_time_parseDay, - H: d3_time_parseHour24, - I: d3_time_parseHour24, - j: d3_time_parseDayOfYear, - L: d3_time_parseMilliseconds, - m: d3_time_parseMonthNumber, - M: d3_time_parseMinutes, - p: d3_time_parseAmPm, - S: d3_time_parseSeconds, - U: d3_time_parseWeekNumberSunday, - w: d3_time_parseWeekdayNumber, - W: d3_time_parseWeekNumberMonday, - x: d3_time_parseLocaleDate, - X: d3_time_parseLocaleTime, - y: d3_time_parseYear, - Y: d3_time_parseFullYear, - Z: d3_time_parseZone, - "%": d3_time_parseLiteralPercent - }; - function d3_time_parseWeekdayAbbrev(date, string, i) { - d3_time_dayAbbrevRe.lastIndex = 0; - var n = d3_time_dayAbbrevRe.exec(string.substring(i)); - return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - function d3_time_parseWeekday(date, string, i) { - d3_time_dayRe.lastIndex = 0; - var n = d3_time_dayRe.exec(string.substring(i)); - return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - function d3_time_parseWeekdayNumber(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 1)); - return n ? (date.w = +n[0], i + n[0].length) : -1; - } - function d3_time_parseWeekNumberSunday(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i)); - return n ? (date.U = +n[0], i + n[0].length) : -1; - } - function d3_time_parseWeekNumberMonday(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i)); - return n ? (date.W = +n[0], i + n[0].length) : -1; - } - function d3_time_parseMonthAbbrev(date, string, i) { - d3_time_monthAbbrevRe.lastIndex = 0; - var n = d3_time_monthAbbrevRe.exec(string.substring(i)); - return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - function d3_time_parseMonth(date, string, i) { - d3_time_monthRe.lastIndex = 0; - var n = d3_time_monthRe.exec(string.substring(i)); - return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - function d3_time_parseLocaleFull(date, string, i) { - return d3_time_parse(date, d3_time_formats.c.toString(), string, i); - } - function d3_time_parseLocaleDate(date, string, i) { - return d3_time_parse(date, d3_time_formats.x.toString(), string, i); - } - function d3_time_parseLocaleTime(date, string, i) { - return d3_time_parse(date, d3_time_formats.X.toString(), string, i); - } - function d3_time_parseFullYear(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 4)); - return n ? (date.y = +n[0], i + n[0].length) : -1; - } - function d3_time_parseYear(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; - } - function d3_time_parseZone(date, string, i) { - return /^[+-]\d{4}$/.test(string = string.substring(i, i + 5)) ? (date.Z = +string, - i + 5) : -1; - } - function d3_time_expandYear(d) { - return d + (d > 68 ? 1900 : 2e3); - } - function d3_time_parseMonthNumber(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.m = n[0] - 1, i + n[0].length) : -1; - } - function d3_time_parseDay(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.d = +n[0], i + n[0].length) : -1; - } - function d3_time_parseDayOfYear(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 3)); - return n ? (date.j = +n[0], i + n[0].length) : -1; - } - function d3_time_parseHour24(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.H = +n[0], i + n[0].length) : -1; - } - function d3_time_parseMinutes(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.M = +n[0], i + n[0].length) : -1; - } - function d3_time_parseSeconds(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.S = +n[0], i + n[0].length) : -1; - } - function d3_time_parseMilliseconds(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 3)); - return n ? (date.L = +n[0], i + n[0].length) : -1; - } - var d3_time_numberRe = /^\s*\d+/; - function d3_time_parseAmPm(date, string, i) { - var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); - return n == null ? -1 : (date.p = n, i); - } - var d3_time_amPmLookup = d3.map({ - am: 0, - pm: 1 - }); - function d3_time_zone(d) { - var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(abs(z) / 60), zm = abs(z) % 60; - return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); - } - function d3_time_parseLiteralPercent(date, string, i) { - d3_time_percentRe.lastIndex = 0; - var n = d3_time_percentRe.exec(string.substring(i, i + 1)); - return n ? i + n[0].length : -1; - } - d3_time_format.utc = d3_time_formatUtc; - function d3_time_formatUtc(template) { - var local = d3_time_format(template); - function format(date) { - try { - d3_date = d3_date_utc; - var utc = new d3_date(); - utc._ = date; - return local(utc); - } finally { - d3_date = Date; - } - } - format.parse = function(string) { - try { - d3_date = d3_date_utc; - var date = local.parse(string); - return date && date._; - } finally { - d3_date = Date; - } - }; - format.toString = local.toString; - return format; - } + var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat; + var d3_time_formatUtc = d3_time_format.utc; var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ"); d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; function d3_time_formatIsoNative(date) { @@ -9194,62 +9391,55 @@ d3 = function() { function d3_time_scaleDate(t) { return new Date(t); } - function d3_time_scaleFormat(formats) { - return function(date) { - var i = formats.length - 1, f = formats[i]; - while (!f[1](date)) f = formats[--i]; - return f[0](date); - }; - } var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ]; - var d3_time_scaleLocalFormats = [ [ d3_time_format("%Y"), d3_true ], [ d3_time_format("%B"), function(d) { - return d.getMonth(); - } ], [ d3_time_format("%b %d"), function(d) { - return d.getDate() != 1; - } ], [ d3_time_format("%a %d"), function(d) { - return d.getDay() && d.getDate() != 1; - } ], [ d3_time_format("%I %p"), function(d) { - return d.getHours(); - } ], [ d3_time_format("%I:%M"), function(d) { - return d.getMinutes(); - } ], [ d3_time_format(":%S"), function(d) { - return d.getSeconds(); - } ], [ d3_time_format(".%L"), function(d) { + var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) { return d.getMilliseconds(); - } ] ]; - var d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); + } ], [ ":%S", function(d) { + return d.getSeconds(); + } ], [ "%I:%M", function(d) { + return d.getMinutes(); + } ], [ "%I %p", function(d) { + return d.getHours(); + } ], [ "%a %d", function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ "%b %d", function(d) { + return d.getDate() != 1; + } ], [ "%B", function(d) { + return d.getMonth(); + } ], [ "%Y", d3_true ] ]); + var d3_time_scaleMilliseconds = { + range: function(start, stop, step) { + return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate); + }, + floor: d3_identity, + ceil: d3_identity + }; d3_time_scaleLocalMethods.year = d3_time.year; d3_time.scale = function() { return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); }; - var d3_time_scaleMilliseconds = { - range: function(start, stop, step) { - return d3.range(+start, +stop, step).map(d3_time_scaleDate); - } - }; - var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { + var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) { return [ m[0].utc, m[1] ]; }); - var d3_time_scaleUTCFormats = [ [ d3_time_formatUtc("%Y"), d3_true ], [ d3_time_formatUtc("%B"), function(d) { - return d.getUTCMonth(); - } ], [ d3_time_formatUtc("%b %d"), function(d) { - return d.getUTCDate() != 1; - } ], [ d3_time_formatUtc("%a %d"), function(d) { - return d.getUTCDay() && d.getUTCDate() != 1; - } ], [ d3_time_formatUtc("%I %p"), function(d) { - return d.getUTCHours(); - } ], [ d3_time_formatUtc("%I:%M"), function(d) { - return d.getUTCMinutes(); - } ], [ d3_time_formatUtc(":%S"), function(d) { - return d.getUTCSeconds(); - } ], [ d3_time_formatUtc(".%L"), function(d) { + var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) { return d.getUTCMilliseconds(); - } ] ]; - var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); - d3_time_scaleUTCMethods.year = d3_time.year.utc; + } ], [ ":%S", function(d) { + return d.getUTCSeconds(); + } ], [ "%I:%M", function(d) { + return d.getUTCMinutes(); + } ], [ "%I %p", function(d) { + return d.getUTCHours(); + } ], [ "%a %d", function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ "%b %d", function(d) { + return d.getUTCDate() != 1; + } ], [ "%B", function(d) { + return d.getUTCMonth(); + } ], [ "%Y", d3_true ] ]); + d3_time_scaleUtcMethods.year = d3_time.year.utc; d3_time.scale.utc = function() { - return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); + return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat); }; d3.text = d3_xhrType(function(request) { return request.responseText; @@ -9271,5 +9461,6 @@ d3 = function() { d3.xml = d3_xhrType(function(request) { return request.responseXML; }); - return d3; + if (typeof define === "function" && define.amd) define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; + this.d3 = d3; }(); \ No newline at end of file diff --git a/awx/ui/static/lib/d3/d3.min.js b/awx/ui/static/lib/d3/d3.min.js new file mode 100644 index 0000000000..e3ee0f9118 --- /dev/null +++ b/awx/ui/static/lib/d3/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n,t){return t>n?-1:n>t?1:n>=t?0:0/0}function t(n){return null===n?0/0:+n}function e(n){return!isNaN(n)}function r(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function u(n){return n.length}function i(n){for(var t=1;n*t%1;)t*=10;return t}function o(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function a(){this._=Object.create(null)}function c(n){return(n+="")===da||n[0]===ma?ma+n:n}function l(n){return(n+="")[0]===ma?n.slice(1):n}function s(n){return c(n)in this._}function f(n){return(n=c(n))in this._&&delete this._[n]}function h(){var n=[];for(var t in this._)n.push(l(t));return n}function g(){var n=0;for(var t in this._)++n;return n}function p(){for(var n in this._)return!1;return!0}function v(){this._=Object.create(null)}function d(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function m(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=ya.length;r>e;++e){var u=ya[e]+t;if(u in n)return u}}function y(){}function M(){}function x(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function O(n){return xa(n,Aa),n}function Y(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var l=Ca.get(n);return l&&(n=l,c=V),a?t?u:r:t?y:i}function Z(n,t){return function(e){var r=ta.event;ta.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ta.event=r}}}function V(n,t){var e=Z(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function X(){var n=".dragsuppress-"+ ++qa,t="click"+n,e=ta.select(oa).on("touchmove"+n,b).on("dragstart"+n,b).on("selectstart"+n,b);if(za){var r=ia.style,u=r[za];r[za]="none"}return function(i){if(e.on(n,null),za&&(r[za]=u),i){var o=function(){e.on(t,null)};e.on(t,function(){b(),o()},!0),setTimeout(o,0)}}}function $(n,t){t.changedTouches&&(t=t.changedTouches[0]);var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>La&&(oa.scrollX||oa.scrollY)){e=ta.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var u=e[0][0].getScreenCTM();La=!(u.f||u.e),e.remove()}return La?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var i=n.getBoundingClientRect();return[t.clientX-i.left-n.clientLeft,t.clientY-i.top-n.clientTop]}function B(){return ta.event.changedTouches[0].identifier}function W(){return ta.event.target}function J(){return oa}function G(n){return n>0?1:0>n?-1:0}function K(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function Q(n){return n>1?0:-1>n?Da:Math.acos(n)}function nt(n){return n>1?ja:-1>n?-ja:Math.asin(n)}function tt(n){return((n=Math.exp(n))-1/n)/2}function et(n){return((n=Math.exp(n))+1/n)/2}function rt(n){return((n=Math.exp(2*n))-1)/(n+1)}function ut(n){return(n=Math.sin(n/2))*n}function it(){}function ot(n,t,e){return this instanceof ot?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ot?new ot(n.h,n.s,n.l):xt(""+n,bt,ot):new ot(n,t,e)}function at(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,new dt(u(n+120),u(n),u(n-120))}function ct(n,t,e){return this instanceof ct?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof ct?new ct(n.h,n.c,n.l):n instanceof st?ht(n.l,n.a,n.b):ht((n=_t((n=ta.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new ct(n,t,e)}function lt(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new st(e,Math.cos(n*=Fa)*t,Math.sin(n)*t)}function st(n,t,e){return this instanceof st?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof st?new st(n.l,n.a,n.b):n instanceof ct?lt(n.h,n.c,n.l):_t((n=dt(n)).r,n.g,n.b):new st(n,t,e)}function ft(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=gt(u)*Ja,r=gt(r)*Ga,i=gt(i)*Ka,new dt(vt(3.2404542*u-1.5371385*r-.4985314*i),vt(-.969266*u+1.8760108*r+.041556*i),vt(.0556434*u-.2040259*r+1.0572252*i))}function ht(n,t,e){return n>0?new ct(Math.atan2(e,t)*Ha,Math.sqrt(t*t+e*e),n):new ct(0/0,0/0,n)}function gt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function pt(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function vt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function dt(n,t,e){return this instanceof dt?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof dt?new dt(n.r,n.g,n.b):xt(""+n,dt,at):new dt(n,t,e)}function mt(n){return new dt(n>>16,255&n>>8,255&n)}function yt(n){return mt(n)+""}function Mt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function xt(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(St(u[0]),St(u[1]),St(u[2]))}return(i=tc.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&i)>>4,o=o>>4|o,a=240&i,a=a>>4|a,c=15&i,c=c<<4|c):7===n.length&&(o=(16711680&i)>>16,a=(65280&i)>>8,c=255&i)),t(o,a,c))}function bt(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),new ot(r,u,c)}function _t(n,t,e){n=wt(n),t=wt(t),e=wt(e);var r=pt((.4124564*n+.3575761*t+.1804375*e)/Ja),u=pt((.2126729*n+.7151522*t+.072175*e)/Ga),i=pt((.0193339*n+.119192*t+.9503041*e)/Ka);return st(116*u-16,500*(r-u),200*(u-i))}function wt(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function St(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function kt(n){return"function"==typeof n?n:function(){return n}}function Et(n){return n}function At(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Nt(t,e,n,r)}}function Nt(n,t,e,r){function u(){var n,t=c.status;if(!t&&zt(c)||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return o.error.call(i,r),void 0}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=ta.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,l=null;return!oa.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=ta.event;ta.event=n;try{o.progress.call(i,c)}finally{ta.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(l=n,i):l},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ra(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var s in a)c.setRequestHeader(s,a[s]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=l&&(c.responseType=l),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},ta.rebind(i,o,"on"),null==r?i:i.get(Ct(r))}function Ct(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function zt(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qt(){var n=Lt(),t=Tt()-n;t>24?(isFinite(t)&&(clearTimeout(ic),ic=setTimeout(qt,t)),uc=0):(uc=1,ac(qt))}function Lt(){var n=Date.now();for(oc=ec;oc;)n>=oc.t&&(oc.f=oc.c(n-oc.t)),oc=oc.n;return n}function Tt(){for(var n,t=ec,e=1/0;t;)t.f?t=n?n.n=t.n:ec=t.n:(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Pt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],o=0,a=r[0],c=0;u>0&&a>0&&(c+a+1>t&&(a=Math.max(1,t-c)),i.push(n.substring(u-=a,u+a)),!((c+=a+1)>t));)a=r[o=(o+1)%r.length];return i.reverse().join(e)}:Et;return function(n){var e=lc.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",c=e[4]||"",l=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(l||"0"===r&&"="===o)&&(l=r="0",o="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===c&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=sc.get(g)||Ut;var M=l&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>p){var c=ta.formatPrefix(n,h);n=c.scale(n),e=c.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!l&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===o?u+n+k:">"===o?k+u+n:"^"===o?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Ut(n){return n+""}function jt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Ft(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new hc(e-1)),1),e}function i(n,e){return t(n=new hc(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{hc=jt;var r=new jt;return r._=n,o(r,t,e)}finally{hc=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=Ht(n);return c.floor=c,c.round=Ht(r),c.ceil=Ht(u),c.offset=Ht(i),c.range=a,n}function Ht(n){return function(t,e){try{hc=jt;var r=new jt;return r._=t,n(r,e)._}finally{hc=Date}}}function Ot(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,c=0;++aa;){if(r>=l)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=C[o in pc?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.slice(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,N.c.toString(),t,r)}function c(n,t,r){return e(n,N.x.toString(),t,r)}function l(n,t,r){return e(n,N.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{hc=jt;var t=new hc;return t._=n,r(t)}finally{hc=Date}}var r=t(n);return e.parse=function(n){try{hc=jt;var t=r.parse(n);return t&&t._}finally{hc=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ae;var M=ta.map(),x=It(v),b=Zt(v),_=It(d),w=Zt(d),S=It(m),k=Zt(m),E=It(y),A=Zt(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var N={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Yt(n.getDate(),t,2)},e:function(n,t){return Yt(n.getDate(),t,2)},H:function(n,t){return Yt(n.getHours(),t,2)},I:function(n,t){return Yt(n.getHours()%12||12,t,2)},j:function(n,t){return Yt(1+fc.dayOfYear(n),t,3)},L:function(n,t){return Yt(n.getMilliseconds(),t,3)},m:function(n,t){return Yt(n.getMonth()+1,t,2)},M:function(n,t){return Yt(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Yt(n.getSeconds(),t,2)},U:function(n,t){return Yt(fc.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Yt(fc.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Yt(n.getFullYear()%100,t,2)},Y:function(n,t){return Yt(n.getFullYear()%1e4,t,4)},Z:ie,"%":function(){return"%"}},C={a:r,A:u,b:i,B:o,c:a,d:Qt,e:Qt,H:te,I:te,j:ne,L:ue,m:Kt,M:ee,p:s,S:re,U:Xt,w:Vt,W:$t,x:c,X:l,y:Wt,Y:Bt,Z:Jt,"%":oe};return t}function Yt(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function It(n){return new RegExp("^(?:"+n.map(ta.requote).join("|")+")","i")}function Zt(n){for(var t=new a,e=-1,r=n.length;++e68?1900:2e3)}function Kt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Qt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function ne(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function te(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ee(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function re(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ue(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ie(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=0|va(t)/60,u=va(t)%60;return e+Yt(r,"0",2)+Yt(u,"0",2)}function oe(n,t,e){dc.lastIndex=0;var r=dc.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ae(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,c=Math.cos(t),l=Math.sin(t),s=i*l,f=u*c+s*Math.cos(a),h=s*o*Math.sin(a);_c.add(Math.atan2(h,f)),r=n,u=c,i=l}var t,e,r,u,i;wc.point=function(o,a){wc.point=n,r=(t=o)*Fa,u=Math.cos(a=(e=a)*Fa/2+Da/4),i=Math.sin(a)},wc.lineEnd=function(){n(t,e)}}function pe(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function ve(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function de(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function me(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function ye(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Me(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function xe(n){return[Math.atan2(n[1],n[0]),nt(n[2])]}function be(n,t){return va(n[0]-t[0])a;++a)u.point((e=n[a])[0],e[1]);return u.lineEnd(),void 0}var c=new qe(e,n,null,!0),l=new qe(e,null,c,!1);c.o=l,i.push(c),o.push(l),c=new qe(r,n,null,!1),l=new qe(r,null,c,!0),c.o=l,i.push(c),o.push(l)}}),o.sort(t),ze(i),ze(o),i.length){for(var a=0,c=e,l=o.length;l>a;++a)o[a].e=c=!c;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,l=s.length;l>a;++a)u.point((f=s[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var a=s.length-1;a>=0;--a)u.point((f=s[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function ze(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Te))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:l,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=l,g=ta.merge(g);var n=Fe(m,p);g.length?(b||(i.polygonStart(),b=!0),Ce(g,De,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Re(),x=t(M),b=!1;return y}}function Te(n){return n.length>1}function Re(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:y,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function De(n,t){return((n=n.x)[0]<0?n[1]-ja-Ta:ja-n[1])-((t=t.x)[0]<0?t[1]-ja-Ta:ja-t[1])}function Pe(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?Da:-Da,c=va(i-e);va(c-Da)0?ja:-ja),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=Da&&(va(e-u)Ta?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function je(n,t,e,r){var u;if(null==n)u=e*ja,r.point(-Da,u),r.point(0,u),r.point(Da,u),r.point(Da,0),r.point(Da,-u),r.point(0,-u),r.point(-Da,-u),r.point(-Da,0),r.point(-Da,u);else if(va(n[0]-t[0])>Ta){var i=n[0]a;++a){var l=t[a],s=l.length;if(s)for(var f=l[0],h=f[0],g=f[1]/2+Da/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=l[d];var m=n[0],y=n[1]/2+Da/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>Da,k=p*M;if(_c.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*Pa:b,S^h>=e^m>=e){var E=de(pe(f),pe(n));Me(E);var A=de(u,E);Me(A);var N=(S^b>=0?-1:1)*nt(A[2]);(r>N||r===N&&(E[0]||E[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Ta>i||Ta>i&&0>_c)^1&o}function He(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,c,l,s;return{lineStart:function(){l=c=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?Da:-Da),h):0;if(!e&&(l=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(be(e,g)||be(p,g))&&(p[0]+=Ta,p[1]+=Ta,v=t(p[0],p[1]))),v!==c)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&be(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return s|(l&&c)<<1}}}function r(n,t,e){var r=pe(n),u=pe(t),o=[1,0,0],a=de(r,u),c=ve(a,a),l=a[0],s=c-l*l;if(!s)return!e&&n;var f=i*c/s,h=-i*l/s,g=de(o,a),p=ye(o,f),v=ye(a,h);me(p,v);var d=g,m=ve(p,d),y=ve(d,d),M=m*m-y*(ve(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=ye(d,(-m-x)/y);if(me(b,p),b=xe(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=va(A-Da)A;if(!N&&k>E&&(_=k,k=E,E=_),C?N?k+E>0^b[1]<(va(b[0]-w)Da^(w<=b[0]&&b[0]<=S)){var z=ye(d,(-m+x)/y);return me(z,p),[b,xe(z)]}}}function u(t,e){var r=o?n:Da-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=va(i)>Ta,c=gr(n,6*Fa);return Le(t,e,c,o?[0,-n]:[-Da,n-Da])}function Oe(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,l=o.y,s=a.x,f=a.y,h=0,g=1,p=s-c,v=f-l;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-l,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-l,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:l+h*v}),1>g&&(u.b={x:c+g*p,y:l+g*v}),u}}}}}}function Ye(n,t,e,r){function u(r,u){return va(r[0]-n)0?0:3:va(r[0]-e)0?2:1:va(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],c=a.length,l=a[0];c>o;++o)i=a[o],l[1]<=r?i[1]>r&&K(l,i,n)>0&&++t:i[1]<=r&&K(l,i,n)<0&&--t,l=i;return 0!==t}function l(i,a,c,l){var s=0,f=0;if(null==i||(s=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do l.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+c+4)%4)!==f)}else l.point(a[0],a[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&a.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=0/0}function g(){v&&(p(y,M),x&&w&&A.rejoin(),v.push(A.buffer())),C.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-Uc,Math.min(Uc,n)),t=Math.max(-Uc,Math.min(Uc,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};N(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,E=a,A=Re(),N=Oe(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=ta.merge(v);var t=c([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),l(null,null,1,a),a.lineEnd()),u&&Ce(v,i,t,l,a),a.polygonEnd()),v=d=m=null}};return C}}function Ie(n){var t=0,e=Da/3,r=ir(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*Da/180,e=n[1]*Da/180):[180*(t/Da),180*(e/Da)]},u}function Ze(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,nt((i-(n*n+e*e)*u*u)/(2*u))]},e}function Ve(){function n(n,t){Fc+=u*n-r*t,r=n,u=t}var t,e,r,u;Zc.point=function(i,o){Zc.point=n,t=r=i,e=u=o},Zc.lineEnd=function(){n(t,e)}}function Xe(n,t){Hc>n&&(Hc=n),n>Yc&&(Yc=n),Oc>t&&(Oc=t),t>Ic&&(Ic=t)}function $e(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=Be(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=Be(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Be(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function We(n,t){Ec+=n,Ac+=t,++Nc}function Je(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);Cc+=o*(t+n)/2,zc+=o*(e+r)/2,qc+=o,We(t=n,e=r)}var t,e;Xc.point=function(r,u){Xc.point=n,We(t=r,e=u)}}function Ge(){Xc.point=We}function Ke(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);Cc+=o*(r+n)/2,zc+=o*(u+t)/2,qc+=o,o=u*n-r*t,Lc+=o*(r+n),Tc+=o*(u+t),Rc+=3*o,We(r=n,u=t)}var t,e,r,u;Xc.point=function(i,o){Xc.point=n,We(t=r=i,e=u=o)},Xc.lineEnd=function(){n(t,e)}}function Qe(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Pa)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:y};return a}function nr(n){function t(n){return(a?r:e)(n)}function e(t){return rr(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=0/0,S.point=i,t.lineStart()}function i(e,r){var i=pe([e,r]),o=n(e,r);u(M,x,y,b,_,w,M=o[0],x=o[1],y=e,b=i[0],_=i[1],w=i[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=l,S.lineEnd=s}function l(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,l,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=a+g,_=c+p,w=l+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),E=va(va(w)-1)i||va((y*z+M*q)/x-.5)>.3||o>a*g+c*p+l*v)&&(u(t,e,r,a,c,l,N,C,E,b/=S,_/=S,w,d,m),m.point(N,C),u(N,C,E,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Fa),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function tr(n){var t=nr(function(t,e){return n([t*Ha,e*Ha])});return function(n){return or(t(n))}}function er(n){this.stream=n}function rr(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ur(n){return ir(function(){return n})()}function ir(n){function t(n){return n=a(n[0]*Fa,n[1]*Fa),[n[0]*h+c,l-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(l-n[1])/h),n&&[n[0]*Ha,n[1]*Ha]}function r(){a=Ae(o=lr(m,y,M),i);var n=i(v,d);return c=g-n[0]*h,l=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,o,a,c,l,s,f=nr(function(n,t){return n=i(n,t),[n[0]*h+c,l-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,y=0,M=0,x=Pc,b=Et,_=null,w=null;return t.stream=function(n){return s&&(s.valid=!1),s=or(x(o,f(b(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(x=null==n?(_=n,Pc):He((_=+n)*Fa),u()):_},t.clipExtent=function(n){return arguments.length?(w=n,b=n?Ye(n[0][0],n[0][1],n[1][0],n[1][1]):Et,u()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Fa,d=n[1]%360*Fa,r()):[v*Ha,d*Ha]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Fa,y=n[1]%360*Fa,M=n.length>2?n[2]%360*Fa:0,r()):[m*Ha,y*Ha,M*Ha]},ta.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function or(n){return rr(n,function(t,e){n.point(t*Fa,e*Fa)})}function ar(n,t){return[n,t]}function cr(n,t){return[n>Da?n-Pa:-Da>n?n+Pa:n,t]}function lr(n,t,e){return n?t||e?Ae(fr(n),hr(t,e)):fr(n):t||e?hr(t,e):cr}function sr(n){return function(t,e){return t+=n,[t>Da?t-Pa:-Da>t?t+Pa:t,e]}}function fr(n){var t=sr(n);return t.invert=sr(-n),t}function hr(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*r+a*u;return[Math.atan2(c*i-s*o,a*r-l*u),nt(s*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*i-c*o;return[Math.atan2(c*i+l*o,a*r+s*u),nt(s*r-a*u)]},e}function gr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=pr(e,u),i=pr(e,i),(o>0?i>u:u>i)&&(u+=o*Pa)):(u=n+o*Pa,i=n-.5*c);for(var l,s=u;o>0?s>i:i>s;s-=c)a.point((l=xe([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],l[1])}}function pr(n,t){var e=pe(t);e[0]-=n,Me(e);var r=Q(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Ta)%(2*Math.PI)}function vr(n,t,e){var r=ta.range(n,t-Ta,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function dr(n,t,e){var r=ta.range(n,t-Ta,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function mr(n){return n.source}function yr(n){return n.target}function Mr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),l=u*Math.sin(n),s=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(ut(r-t)+u*o*ut(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*s,u=e*l+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Ha,Math.atan2(o,Math.sqrt(r*r+u*u))*Ha]}:function(){return[n*Ha,t*Ha]};return p.distance=h,p}function xr(){function n(n,u){var i=Math.sin(u*=Fa),o=Math.cos(u),a=va((n*=Fa)-t),c=Math.cos(a);$c+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;Bc.point=function(u,i){t=u*Fa,e=Math.sin(i*=Fa),r=Math.cos(i),Bc.point=n},Bc.lineEnd=function(){Bc.point=Bc.lineEnd=y}}function br(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function _r(n,t){function e(n,t){o>0?-ja+Ta>t&&(t=-ja+Ta):t>ja-Ta&&(t=ja-Ta);var e=o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(Da/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=G(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-ja]},e):Sr}function wr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return va(u)u;u++){for(;r>1&&K(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function zr(n,t){return n[0]-t[0]||n[1]-t[1]}function qr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Lr(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],l=e[1],s=t[1]-c,f=r[1]-l,h=(a*(c-l)-f*(u-i))/(f*o-a*s);return[u+h*o,c+h*s]}function Tr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Rr(){tu(this),this.edge=this.site=this.circle=null}function Dr(n){var t=ol.pop()||new Rr;return t.site=n,t}function Pr(n){Xr(n),rl.remove(n),ol.push(n),tu(n)}function Ur(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Pr(n);for(var c=i;c.circle&&va(e-c.circle.x)s;++s)l=a[s],c=a[s-1],Kr(l.edge,c.site,l.site,u);c=a[0],l=a[f-1],l.edge=Jr(c.site,l.site,null,u),Vr(c),Vr(l)}function jr(n){for(var t,e,r,u,i=n.x,o=n.y,a=rl._;a;)if(r=Fr(a,o)-i,r>Ta)a=a.L;else{if(u=i-Hr(a,o),!(u>Ta)){r>-Ta?(t=a.P,e=a):u>-Ta?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Dr(n);if(rl.insert(t,c),t||e){if(t===e)return Xr(t),e=Dr(t.site),rl.insert(c,e),c.edge=e.edge=Jr(t.site,c.site),Vr(t),Vr(e),void 0;if(!e)return c.edge=Jr(t.site,c.site),void 0;Xr(t),Xr(e);var l=t.site,s=l.x,f=l.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};Kr(e.edge,l,p,x),c.edge=Jr(l,n,null,x),e.edge=Jr(n,p,null,x),Vr(t),Vr(e)}}function Fr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,l=c-t;if(!l)return a;var s=a-r,f=1/i-1/l,h=s/l;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*l)-c+l/2+u-i/2)))/f+r:(r+a)/2}function Hr(n,t){var e=n.N;if(e)return Fr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Or(n){this.site=n,this.edges=[]}function Yr(n){for(var t,e,r,u,i,o,a,c,l,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=el,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)s=a[o].end(),r=s.x,u=s.y,l=a[++o%c].start(),t=l.x,e=l.y,(va(r-t)>Ta||va(u-e)>Ta)&&(a.splice(o,0,new Qr(Gr(i.site,s,va(r-f)Ta?{x:f,y:va(t-f)Ta?{x:va(e-p)Ta?{x:h,y:va(t-h)Ta?{x:va(e-g)=-Ra)){var g=c*c+l*l,p=s*s+f*f,v=(f*g-l*p)/h,d=(c*p-s*g)/h,f=d+a,m=al.pop()||new Zr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=il._;M;)if(m.yd||d>=a)return;if(h>p){if(i){if(i.y>=l)return}else i={x:d,y:c};e={x:d,y:l}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=l)return}else i={x:(c-u)/r,y:c};e={x:(l-u)/r,y:l}}else{if(i){if(i.yg){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.xi||f>o||r>h||u>g)){if(p=n.point){var p,v=t-p[0],d=e-p[1],m=v*v+d*d;if(c>m){var y=Math.sqrt(c=m);r=t-y,u=e-y,i=t+y,o=e+y,a=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:l(n,s,f,x,b);break;case 1:l(n,x,f,h,b);break;case 2:l(n,s,b,x,g);break;case 3:l(n,x,b,h,g)}}}(n,r,u,i,o),a}function gu(n,t){n=ta.rgb(n),t=ta.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+Mt(Math.round(e+i*n))+Mt(Math.round(r+o*n))+Mt(Math.round(u+a*n))}}function pu(n,t){var e,r={},u={};for(e in n)e in t?r[e]=mu(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function vu(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function du(n,t){var e,r,u,i=ll.lastIndex=sl.lastIndex=0,o=-1,a=[],c=[];for(n+="",t+="";(e=ll.exec(n))&&(r=sl.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),a[o]?a[o]+=u:a[++o]=u),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,c.push({i:o,x:vu(e,r)})),i=sl.lastIndex;return ir;++r)a[(e=c[r]).i]=e.x(n);return a.join("")})}function mu(n,t){for(var e,r=ta.interpolators.length;--r>=0&&!(e=ta.interpolators[r](n,t)););return e}function yu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(mu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function Mu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function xu(n){return function(t){return 1-n(1-t)}}function bu(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function _u(n){return n*n}function wu(n){return n*n*n}function Su(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function ku(n){return function(t){return Math.pow(t,n)}}function Eu(n){return 1-Math.cos(n*ja)}function Au(n){return Math.pow(2,10*(n-1))}function Nu(n){return 1-Math.sqrt(1-n*n)}function Cu(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Pa*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Pa/t)}}function zu(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function qu(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Lu(n,t){n=ta.hcl(n),t=ta.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return lt(e+i*n,r+o*n,u+a*n)+""}}function Tu(n,t){n=ta.hsl(n),t=ta.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return at(e+i*n,r+o*n,u+a*n)+""}}function Ru(n,t){n=ta.lab(n),t=ta.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ft(e+i*n,r+o*n,u+a*n)+""}}function Du(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Pu(n){var t=[n.a,n.b],e=[n.c,n.d],r=ju(t),u=Uu(t,e),i=ju(Fu(e,t,-u))||0;t[0]*e[1]180?s+=360:s-l>180&&(l+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:vu(l,s)})):s&&r.push(r.pop()+"rotate("+s+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:vu(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:vu(g[0],p[0])},{i:e-2,x:vu(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i=0;)e.push(u[r])}function Qu(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,o=-1;++oe;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function si(n){return n.reduce(fi,0)}function fi(n,t){return n+t[1]}function hi(n,t){return gi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function gi(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function pi(n){return[ta.min(n),ta.max(n)]}function vi(n,t){return n.value-t.value}function di(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function mi(n,t){n._pack_next=t,t._pack_prev=n}function yi(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Mi(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(l=e.length)){var e,r,u,i,o,a,c,l,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(xi),r=e[0],r.x=-r.r,r.y=0,t(r),l>1&&(u=e[1],u.x=u.r,u.y=0,t(u),l>2))for(i=e[2],wi(r,u,i),t(i),di(r,i),r._pack_prev=i,di(i,u),u=r._pack_next,o=3;l>o;o++){wi(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(yi(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!yi(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.ro;o++)i=e[o],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(bi)}}function xi(n){n._pack_next=n._pack_prev=n}function bi(n){delete n._pack_next,delete n._pack_prev}function _i(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Ci(n,t,e){return n.a.parent===t.parent?n.a:e}function zi(n){return 1+ta.max(n,function(n){return n.y})}function qi(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Li(n){var t=n.children;return t&&t.length?Li(t[0]):n}function Ti(n){var t,e=n.children;return e&&(t=e.length)?Ti(e[t-1]):n}function Ri(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Di(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Pi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Ui(n){return n.rangeExtent?n.rangeExtent():Pi(n.range())}function ji(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Fi(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Hi(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:bl}function Oi(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Oi:ji,c=r?Yu:Ou;return o=u(n,t,c,e),a=u(t,n,c,mu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Du)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Xi(n,t)},i.tickFormat=function(t,e){return $i(n,t,e)},i.nice=function(t){return Zi(n,t),u()},i.copy=function(){return Yi(n,t,e,r)},u()}function Ii(n,t){return ta.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Zi(n,t){return Fi(n,Hi(Vi(n,t)[2]))}function Vi(n,t){null==t&&(t=10);var e=Pi(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Xi(n,t){return ta.range.apply(ta,Vi(n,t))}function $i(n,t,e){var r=Vi(n,t);if(e){var u=lc.exec(e);if(u.shift(),"s"===u[8]){var i=ta.formatPrefix(Math.max(va(r[0]),va(r[1])));return u[7]||(u[7]="."+Bi(i.scale(r[2]))),u[8]="f",e=ta.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+Wi(u[8],r)),e=u.join("")}else e=",."+Bi(r[2])+"f";return ta.format(e)}function Bi(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function Wi(n,t){var e=Bi(t[2]);return n in _l?Math.abs(e-Bi(Math.max(va(t[0]),va(t[1]))))+ +("e"!==n):e-2*("%"===n)}function Ji(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Fi(r.map(u),e?Math:Sl);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Pi(r),o=[],a=n[0],c=n[1],l=Math.floor(u(a)),s=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(s-l)){if(e){for(;s>l;l++)for(var h=1;f>h;h++)o.push(i(l)*h);o.push(i(l))}else for(o.push(i(l));l++0;h--)o.push(i(l)*h);for(l=0;o[l]c;s--);o=o.slice(l,s)}return o},o.tickFormat=function(n,t){if(!arguments.length)return wl;arguments.length<2?t=wl:"function"!=typeof t&&(t=ta.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return Ji(n.copy(),t,e,r)},Ii(o,n)}function Gi(n,t,e){function r(t){return n(u(t))}var u=Ki(t),i=Ki(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Xi(e,n)},r.tickFormat=function(n,t){return $i(e,n,t)},r.nice=function(n){return r.domain(Zi(e,n))},r.exponent=function(o){return arguments.length?(u=Ki(t=o),i=Ki(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return Gi(n.copy(),t,e)},Ii(r,n)}function Ki(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function Qi(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):0/0))-1)%i.length]}function r(t,e){return ta.range(n.length).map(function(n){return t+e*n})}var u,i,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new a;for(var i,o=-1,c=r.length;++on?[0/0,0/0]:[n>0?a[n-1]:r[0],nt?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return to(n,t,e)},u()}function eo(n,t){function e(e){return e>=e?t[ta.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return eo(n,t)},e}function ro(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Xi(n,t)},t.tickFormat=function(t,e){return $i(n,t,e)},t.copy=function(){return ro(n)},t}function uo(){return 0}function io(n){return n.innerRadius}function oo(n){return n.outerRadius}function ao(n){return n.startAngle}function co(n){return n.endAngle}function lo(n){return n&&n.padAngle}function so(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function fo(n,t,e,r,u){var i=n[0]-t[0],o=n[1]-t[1],a=(u?r:-r)/Math.sqrt(i*i+o*o),c=a*o,l=-a*i,s=n[0]+c,f=n[1]+l,h=t[0]+c,g=t[1]+l,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(M*M*y-x*x),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,E=_-p,A=w-v,N=S-p,C=k-v;return E*E+A*A>N*N+C*C&&(_=S,w=k),[[_-c,w-l],[_*e/M,w*e/M]]}function ho(n){function t(t){function o(){l.push("M",i(n(s),a))}for(var c,l=[],s=[],f=-1,h=t.length,g=kt(e),p=kt(r);++f1&&u.push("H",r[0]),u.join("")}function mo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var l=2;l9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function To(n){return n.length<3?go(n):n[0]+_o(n,Lo(n))}function Ro(n){for(var t,e,r,u=-1,i=n.length;++ur)return s();var u=i[i.active];u&&(--i.count,delete i[i.active],u.event&&u.event.interrupt.call(n,n.__data__,u.index)),i.active=r,o.event&&o.event.start.call(n,n.__data__,t),o.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&v.push(r)}),h=o.ease,f=o.duration,ta.timer(function(){return p.c=l(e||1)?Ne:l,1},0,c)}function l(e){if(i.active!==r)return 1;for(var u=e/f,a=h(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,n.__data__,t),s()):void 0}function s(){return--i.count?delete i[r]:delete n[e],1}var f,h,g=o.delay,p=oc,v=[];return p.t=g+c,u>=g?a(u-g):(p.c=a,void 0)},0,c)}}function Bo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function Wo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function Jo(n){return n.toISOString()}function Go(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=ta.bisect(Wl,u);return i==Wl.length?[t.year,Vi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Wl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=Ko(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Ko(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Pi(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Ko(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Go(n.copy(),t,e)},Ii(r,n)}function Ko(n){return new Date(n)}function Qo(n){return JSON.parse(n.responseText)}function na(n){var t=ua.createRange();return t.selectNode(ua.body),t.createContextualFragment(n.responseText)}var ta={version:"3.5.3"};Date.now||(Date.now=function(){return+new Date});var ea=[].slice,ra=function(n){return ea.call(n)},ua=document,ia=ua.documentElement,oa=window;try{ra(ia.childNodes)[0].nodeType}catch(aa){ra=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}try{ua.createElement("div").style.setProperty("opacity",0,"")}catch(ca){var la=oa.Element.prototype,sa=la.setAttribute,fa=la.setAttributeNS,ha=oa.CSSStyleDeclaration.prototype,ga=ha.setProperty;la.setAttribute=function(n,t){sa.call(this,n,t+"")},la.setAttributeNS=function(n,t,e){fa.call(this,n,t,e+"")},ha.setProperty=function(n,t,e){ga.call(this,n,t+"",e)}}ta.ascending=n,ta.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},ta.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},ta.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},ta.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},ta.sum=function(n,t){var r,u=0,i=n.length,o=-1;if(1===arguments.length)for(;++o1?c/(s-1):void 0},ta.deviation=function(){var n=ta.variance.apply(this,arguments);return n?Math.sqrt(n):n};var pa=r(n);ta.bisectLeft=pa.left,ta.bisect=ta.bisectRight=pa.right,ta.bisector=function(t){return r(1===t.length?function(e,r){return n(t(e),r)}:t)},ta.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=0|Math.random()*i--,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},ta.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ta.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},ta.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=ta.min(arguments,u),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var va=Math.abs;ta.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),1/0===(t-n)/e)throw new Error("infinite range");var r,u=[],o=i(va(e)),a=-1;if(n*=o,t*=o,e*=o,0>e)for(;(r=n+e*++a)>t;)u.push(r/o);else for(;(r=n+e*++a)=i.length)return r?r.call(u,o):e?o.sort(e):o;for(var l,s,f,h,g=-1,p=o.length,v=i[c++],d=new a;++g=i.length)return n;var r=[],u=o[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ta.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return o[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ta.set=function(n){var t=new v;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},o(v,{has:s,add:function(n){return this._[c(n+="")]=!0,n},remove:f,values:h,size:g,empty:p,forEach:function(n){for(var t in this._)n.call(this,l(t))}}),ta.behavior={},ta.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ta.event=null,ta.requote=function(n){return n.replace(Ma,"\\$&")};var Ma=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,xa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ba=function(n,t){return t.querySelector(n)},_a=function(n,t){return t.querySelectorAll(n)},wa=ia.matches||ia[m(ia,"matchesSelector")],Sa=function(n,t){return wa.call(n,t)};"function"==typeof Sizzle&&(ba=function(n,t){return Sizzle(n,t)[0]||null},_a=Sizzle,Sa=Sizzle.matchesSelector),ta.selection=function(){return Na};var ka=ta.selection.prototype=[];ka.select=function(n){var t,e,r,u,i=[];n=k(n);for(var o=-1,a=this.length;++o=0&&(e=n.slice(0,t),n=n.slice(t+1)),Ea.hasOwnProperty(e)?{space:Ea[e],local:n}:n}},ka.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ta.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(A(t,n[t]));return this}return this.each(A(n,t))},ka.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=z(n)).length,u=-1;if(t=e.classList){for(;++ur){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(T(e,n[e],t));return this}if(2>r)return oa.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(T(n,t,e))},ka.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(R(t,n[t]));return this}return this.each(R(n,t))},ka.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},ka.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},ka.append=function(n){return n=D(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},ka.insert=function(n,t){return n=D(n),t=k(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},ka.remove=function(){return this.each(P)},ka.data=function(n,t){function e(n,e){var r,u,i,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),v=new Array(o);if(t){var d,m=new a,y=new Array(o);for(r=-1;++rr;++r)p[r]=U(e[r]);for(;o>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,c.push(p),l.push(g),s.push(v)}var r,u,i=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return S(u)},ka.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},ka.sort=function(n){n=F.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},ka.size=function(){var n=0;return H(this,function(){++n}),n};var Aa=[];ta.selection.enter=O,ta.selection.enter.prototype=Aa,Aa.append=ka.append,Aa.empty=ka.empty,Aa.node=ka.node,Aa.call=ka.call,Aa.size=ka.size,Aa.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(I(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(I(n,t,e))};var Ca=ta.map({mouseenter:"mouseover",mouseleave:"mouseout"});Ca.forEach(function(n){"on"+n in ua&&Ca.remove(n)});var za="onselectstart"in ua?null:m(ia.style,"userSelect"),qa=0;ta.mouse=function(n){return $(n,_())};var La=/WebKit/.test(oa.navigator.userAgent)?-1:0;ta.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=_().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return $(n,r)},ta.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",i)}function t(n,t,u,i,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+l[0],y:r[1]+l[1],dx:n,dy:e}))}function c(){t(h,v)&&(m.on(i+d,null).on(o+d,null),y(p&&ta.event.target===f),g({type:"dragend"}))}var l,s=this,f=ta.event.target,h=s.parentNode,g=e.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=ta.select(u()).on(i+d,a).on(o+d,c),y=X(),M=t(h,v);r?(l=r.apply(s,arguments),l=[l.x-M[0],l.y-M[1]]):l=[0,0],g({type:"dragstart"})}}var e=w(n,"drag","dragstart","dragend"),r=null,u=t(y,ta.mouse,J,"mousemove","mouseup"),i=t(B,ta.touch,W,"touchmove","touchend");return n.origin=function(t){return arguments.length?(r=t,n):r},ta.rebind(n,e,"on")},ta.touches=function(n,t){return arguments.length<2&&(t=_().touches),t?ra(t).map(function(t){var e=$(n,t);return e.identifier=t.identifier,e}):[]};var Ta=1e-6,Ra=Ta*Ta,Da=Math.PI,Pa=2*Da,Ua=Pa-Ta,ja=Da/2,Fa=Da/180,Ha=180/Da,Oa=Math.SQRT2,Ya=2,Ia=4;ta.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=et(v),o=i/(Ya*h)*(e*rt(Oa*t+v)-tt(v));return[r+o*l,u+o*s,i*e/et(Oa*t+v)]}return[r+n*l,u+n*s,i*Math.exp(Oa*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],l=o-r,s=a-u,f=l*l+s*s,h=Math.sqrt(f),g=(c*c-i*i+Ia*f)/(2*i*Ya*h),p=(c*c-i*i-Ia*f)/(2*c*Ya*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/Oa;return e.duration=1e3*y,e},ta.behavior.zoom=function(){function n(n){n.on(z,s).on(Xa+".zoom",h).on("dblclick.zoom",g).on(T,f)}function t(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function e(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function r(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=e(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function i(t,e,i,o){t.__chart__={x:k.x,y:k.y,k:k.k},r(Math.pow(2,o)),u(v=e,i),t=ta.select(t),N>0&&(t=t.transition().duration(N)),t.call(n.event)}function o(){x&&x.domain(M.range().map(function(n){return(n-k.x)/k.k}).map(M.invert)),S&&S.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function a(n){C++||n({type:"zoomstart"})}function c(n){o(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function l(n){--C||n({type:"zoomend"}),v=null}function s(){function n(){s=1,u(ta.mouse(r),h),c(o)}function e(){f.on(q,null).on(L,null),g(s&&ta.event.target===i),l(o)}var r=this,i=ta.event.target,o=R.of(r,arguments),s=0,f=ta.select(oa).on(q,n).on(L,e),h=t(ta.mouse(r)),g=X();Fl.call(r),a(o)}function f(){function n(){var n=ta.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=t(n))}),n}function e(){var t=ta.event.target;ta.select(t).on(x,o).on(_,h),w.push(t);for(var e=ta.event.changedTouches,r=0,u=e.length;u>r;++r)d[e[r].identifier]=null;var a=n(),c=Date.now();if(1===a.length){if(500>c-y){var l=a[0];i(p,l,d[l.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),b()}y=c}else if(a.length>1){var l=a[0],s=a[1],f=l[0]-s[0],g=l[1]-s[1];m=f*f+g*g}}function o(){var n,t,e,i,o=ta.touches(p);Fl.call(p);for(var a=0,l=o.length;l>a;++a,i=null)if(e=o[a],i=d[e.identifier]){if(t)break;n=e,t=i}if(i){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+i[0])/2,(t[1]+i[1])/2],r(f*g)}y=null,u(n,t),c(v)}function h(){if(ta.event.touches.length){for(var t=ta.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}ta.selectAll(w).on(M,null),S.on(z,s).on(T,f),E(),l(v)}var g,p=this,v=R.of(p,arguments),d={},m=0,M=".zoom-"+ta.event.changedTouches[0].identifier,x="touchmove"+M,_="touchend"+M,w=[],S=ta.select(p),E=X();e(),a(v),S.on(z,null).on(T,e)}function h(){var n=R.of(this,arguments);m?clearTimeout(m):(p=t(v=d||ta.mouse(this)),Fl.call(this),a(n)),m=setTimeout(function(){m=null,l(n)},50),b(),r(Math.pow(2,.002*Za())*k.k),u(v,p),c(n)}function g(){var n=ta.mouse(this),e=Math.log(k.k)/Math.LN2;i(this,n,t(n),ta.event.shiftKey?Math.ceil(e)-1:Math.floor(e)+1)}var p,v,d,m,y,M,x,_,S,k={x:0,y:0,k:1},E=[960,500],A=Va,N=250,C=0,z="mousedown.zoom",q="mousemove.zoom",L="mouseup.zoom",T="touchstart.zoom",R=w(n,"zoomstart","zoom","zoomend");return n.event=function(n){n.each(function(){var n=R.of(this,arguments),t=k;Ul?ta.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},a(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],u=v?v[0]:e/2,i=v?v[1]:r/2,o=ta.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:u-r[0]*a,y:i-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){l(n)}).each("end.zoom",function(){l(n)}):(this.__chart__=k,a(n),c(n),l(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},o(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:+t},o(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Va:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(d=t&&[+t[0],+t[1]],n):d},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(N=+t,n):N},n.x=function(t){return arguments.length?(x=t,M=t.copy(),k={x:0,y:0,k:1},n):x},n.y=function(t){return arguments.length?(S=t,_=t.copy(),k={x:0,y:0,k:1},n):S},ta.rebind(n,R,"on")};var Za,Va=[0,1/0],Xa="onwheel"in ua?(Za=function(){return-ta.event.deltaY*(ta.event.deltaMode?120:1)},"wheel"):"onmousewheel"in ua?(Za=function(){return ta.event.wheelDelta},"mousewheel"):(Za=function(){return-ta.event.detail},"MozMousePixelScroll");ta.color=it,it.prototype.toString=function(){return this.rgb()+""},ta.hsl=ot;var $a=ot.prototype=new it;$a.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ot(this.h,this.s,this.l/n)},$a.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ot(this.h,this.s,n*this.l)},$a.rgb=function(){return at(this.h,this.s,this.l)},ta.hcl=ct;var Ba=ct.prototype=new it;Ba.brighter=function(n){return new ct(this.h,this.c,Math.min(100,this.l+Wa*(arguments.length?n:1)))},Ba.darker=function(n){return new ct(this.h,this.c,Math.max(0,this.l-Wa*(arguments.length?n:1)))},Ba.rgb=function(){return lt(this.h,this.c,this.l).rgb()},ta.lab=st;var Wa=18,Ja=.95047,Ga=1,Ka=1.08883,Qa=st.prototype=new it;Qa.brighter=function(n){return new st(Math.min(100,this.l+Wa*(arguments.length?n:1)),this.a,this.b)},Qa.darker=function(n){return new st(Math.max(0,this.l-Wa*(arguments.length?n:1)),this.a,this.b)},Qa.rgb=function(){return ft(this.l,this.a,this.b)},ta.rgb=dt;var nc=dt.prototype=new it;nc.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new dt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new dt(u,u,u)},nc.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new dt(n*this.r,n*this.g,n*this.b)},nc.hsl=function(){return bt(this.r,this.g,this.b)},nc.toString=function(){return"#"+Mt(this.r)+Mt(this.g)+Mt(this.b)};var tc=ta.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});tc.forEach(function(n,t){tc.set(n,mt(t))}),ta.functor=kt,ta.xhr=At(Et),ta.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=Nt(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),c=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=l)return o;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++a);else if(r!==c)continue;return n.slice(t,s-a)}return n.slice(t)}for(var r,u,i={},o={},a=[],l=n.length,s=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,f++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new v,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},ta.csv=ta.dsv(",","text/csv"),ta.tsv=ta.dsv(" ","text/tab-separated-values");var ec,rc,uc,ic,oc,ac=oa[m(oa,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ta.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};rc?rc.n=i:ec=i,rc=i,uc||(ic=clearTimeout(ic),uc=1,ac(qt))},ta.timer.flush=function(){Lt(),Tt()},ta.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var cc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Dt);ta.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=ta.round(n,Rt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),cc[8+e/3]};var lc=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,sc=ta.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ta.round(n,Rt(n,t))).toFixed(Math.max(0,Math.min(20,Rt(n*(1+1e-15),t))))}}),fc=ta.time={},hc=Date;jt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){gc.setUTCDate.apply(this._,arguments)},setDay:function(){gc.setUTCDay.apply(this._,arguments)},setFullYear:function(){gc.setUTCFullYear.apply(this._,arguments)},setHours:function(){gc.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){gc.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){gc.setUTCMinutes.apply(this._,arguments)},setMonth:function(){gc.setUTCMonth.apply(this._,arguments)},setSeconds:function(){gc.setUTCSeconds.apply(this._,arguments)},setTime:function(){gc.setTime.apply(this._,arguments)}};var gc=Date.prototype;fc.year=Ft(function(n){return n=fc.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),fc.years=fc.year.range,fc.years.utc=fc.year.utc.range,fc.day=Ft(function(n){var t=new hc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),fc.days=fc.day.range,fc.days.utc=fc.day.utc.range,fc.dayOfYear=function(n){var t=fc.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=fc[n]=Ft(function(n){return(n=fc.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=fc.year(n).getDay();return Math.floor((fc.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});fc[n+"s"]=e.range,fc[n+"s"].utc=e.utc.range,fc[n+"OfYear"]=function(n){var e=fc.year(n).getDay();return Math.floor((fc.dayOfYear(n)+(e+t)%7)/7)}}),fc.week=fc.sunday,fc.weeks=fc.sunday.range,fc.weeks.utc=fc.sunday.utc.range,fc.weekOfYear=fc.sundayOfYear;var pc={"-":"",_:" ",0:"0"},vc=/^\s*\d+/,dc=/^%/;ta.locale=function(n){return{numberFormat:Pt(n),timeFormat:Ot(n)}};var mc=ta.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ta.format=mc.numberFormat,ta.geo={},ce.prototype={s:0,t:0,add:function(n){le(n,this.t,yc),le(yc.s,this.s,this),this.s?this.t+=yc.t:this.s=yc.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var yc=new ce;ta.geo.stream=function(n,t){n&&Mc.hasOwnProperty(n.type)?Mc[n.type](n,t):se(n,t)};var Mc={Feature:function(n,t){se(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*Da+n:n,wc.lineStart=wc.lineEnd=wc.point=y}};ta.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=pe([t*Fa,e*Fa]);if(m){var u=de(m,r),i=[u[1],-u[0],0],o=de(i,u);Me(o),o=xe(o);var c=t-p,l=c>0?1:-1,v=o[0]*Ha*l,d=va(c)>180;if(d^(v>l*p&&l*t>v)){var y=o[1]*Ha;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>l*p&&l*t>v)){var y=-o[1]*Ha;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=va(r)>180?r+(r>0?360:-360):r}else v=n,d=e;wc.point(n,e),t(n,e)}function i(){wc.lineStart()}function o(){u(v,d),wc.lineEnd(),va(y)>Ta&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function l(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:n_c?(s=-(h=180),f=-(g=90)):y>Ta?g=90:-Ta>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],ta.geo.stream(n,b);var t=M.length;if(t){M.sort(c);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],l(e[0],u)||l(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,s=e[0],h=u[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),ta.geo.centroid=function(n){Sc=kc=Ec=Ac=Nc=Cc=zc=qc=Lc=Tc=Rc=0,ta.geo.stream(n,Dc);var t=Lc,e=Tc,r=Rc,u=t*t+e*e+r*r;return Ra>u&&(t=Cc,e=zc,r=qc,Ta>kc&&(t=Ec,e=Ac,r=Nc),u=t*t+e*e+r*r,Ra>u)?[0/0,0/0]:[Math.atan2(e,t)*Ha,nt(r/Math.sqrt(u))*Ha]};var Sc,kc,Ec,Ac,Nc,Cc,zc,qc,Lc,Tc,Rc,Dc={sphere:y,point:_e,lineStart:Se,lineEnd:ke,polygonStart:function(){Dc.lineStart=Ee},polygonEnd:function(){Dc.lineStart=Se}},Pc=Le(Ne,Pe,je,[-Da,-Da/2]),Uc=1e9;ta.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Ye(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ta.geo.conicEqualArea=function(){return Ie(Ze)}).raw=Ze,ta.geo.albers=function(){return ta.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ta.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=ta.geo.albers(),o=ta.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ta.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var l=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*l,f-.238*l],[s+.455*l,f+.238*l]]).stream(c).point,r=o.translate([s-.307*l,f+.201*l]).clipExtent([[s-.425*l+Ta,f+.12*l+Ta],[s-.214*l-Ta,f+.234*l-Ta]]).stream(c).point,u=a.translate([s-.205*l,f+.212*l]).clipExtent([[s-.214*l+Ta,f+.166*l+Ta],[s-.115*l-Ta,f+.234*l-Ta]]).stream(c).point,n},n.scale(1070)};var jc,Fc,Hc,Oc,Yc,Ic,Zc={point:y,lineStart:y,lineEnd:y,polygonStart:function(){Fc=0,Zc.lineStart=Ve},polygonEnd:function(){Zc.lineStart=Zc.lineEnd=Zc.point=y,jc+=va(Fc/2)}},Vc={point:Xe,lineStart:y,lineEnd:y,polygonStart:y,polygonEnd:y},Xc={point:We,lineStart:Je,lineEnd:Ge,polygonStart:function(){Xc.lineStart=Ke},polygonEnd:function(){Xc.point=We,Xc.lineStart=Je,Xc.lineEnd=Ge}};ta.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),ta.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return jc=0,ta.geo.stream(n,u(Zc)),jc},n.centroid=function(n){return Ec=Ac=Nc=Cc=zc=qc=Lc=Tc=Rc=0,ta.geo.stream(n,u(Xc)),Rc?[Lc/Rc,Tc/Rc]:qc?[Cc/qc,zc/qc]:Nc?[Ec/Nc,Ac/Nc]:[0/0,0/0]},n.bounds=function(n){return Yc=Ic=-(Hc=Oc=1/0),ta.geo.stream(n,u(Vc)),[[Hc,Oc],[Yc,Ic]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||tr(n):Et,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new $e:new Qe(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(ta.geo.albersUsa()).context(null)},ta.geo.transform=function(n){return{stream:function(t){var e=new er(t);for(var r in n)e[r]=n[r];return e}}},er.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ta.geo.projection=ur,ta.geo.projectionMutator=ir,(ta.geo.equirectangular=function(){return ur(ar)}).raw=ar.invert=ar,ta.geo.rotation=function(n){function t(t){return t=n(t[0]*Fa,t[1]*Fa),t[0]*=Ha,t[1]*=Ha,t}return n=lr(n[0]%360*Fa,n[1]*Fa,n.length>2?n[2]*Fa:0),t.invert=function(t){return t=n.invert(t[0]*Fa,t[1]*Fa),t[0]*=Ha,t[1]*=Ha,t},t},cr.invert=ar,ta.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=lr(-n[0]*Fa,-n[1]*Fa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ha,n[1]*=Ha}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=gr((t=+r)*Fa,u*Fa),n):t},n.precision=function(r){return arguments.length?(e=gr(t*Fa,(u=+r)*Fa),n):u},n.angle(90)},ta.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Fa,u=n[1]*Fa,i=t[1]*Fa,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),l=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=l*s-c*f*a)*e),c*s+l*f*a)},ta.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ta.range(Math.ceil(i/d)*d,u,d).map(h).concat(ta.range(Math.ceil(l/m)*m,c,m).map(g)).concat(ta.range(Math.ceil(r/p)*p,e,p).filter(function(n){return va(n%d)>Ta}).map(s)).concat(ta.range(Math.ceil(a/v)*v,o,v).filter(function(n){return va(n%m)>Ta}).map(f))}var e,r,u,i,o,a,c,l,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(l).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],l=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),l>c&&(t=l,l=c,c=t),n.precision(y)):[[i,l],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=vr(a,o,90),f=dr(r,e,y),h=vr(l,c,90),g=dr(i,u,y),n):y},n.majorExtent([[-180,-90+Ta],[180,90-Ta]]).minorExtent([[-180,-80-Ta],[180,80+Ta]])},ta.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=mr,u=yr;return n.distance=function(){return ta.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},ta.geo.interpolate=function(n,t){return Mr(n[0]*Fa,n[1]*Fa,t[0]*Fa,t[1]*Fa)},ta.geo.length=function(n){return $c=0,ta.geo.stream(n,Bc),$c};var $c,Bc={sphere:y,point:y,lineStart:xr,lineEnd:y,polygonStart:y,polygonEnd:y},Wc=br(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ta.geo.azimuthalEqualArea=function(){return ur(Wc)}).raw=Wc;var Jc=br(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},Et);(ta.geo.azimuthalEquidistant=function(){return ur(Jc)}).raw=Jc,(ta.geo.conicConformal=function(){return Ie(_r)}).raw=_r,(ta.geo.conicEquidistant=function(){return Ie(wr)}).raw=wr;var Gc=br(function(n){return 1/n},Math.atan);(ta.geo.gnomonic=function(){return ur(Gc)}).raw=Gc,Sr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-ja]},(ta.geo.mercator=function(){return kr(Sr)}).raw=Sr;var Kc=br(function(){return 1},Math.asin);(ta.geo.orthographic=function(){return ur(Kc)}).raw=Kc;var Qc=br(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ta.geo.stereographic=function(){return ur(Qc)}).raw=Qc,Er.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-ja]},(ta.geo.transverseMercator=function(){var n=kr(Er),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Er,ta.geom={},ta.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=kt(e),i=kt(r),o=n.length,a=[],c=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(zr),t=0;o>t;t++)c.push([a[t][0],-a[t][1]]);var l=Cr(a),s=Cr(c),f=s[0]===l[0],h=s[s.length-1]===l[l.length-1],g=[];for(t=l.length-1;t>=0;--t)g.push(n[a[l[t]][2]]);for(t=+f;t=r&&l.x<=i&&l.y>=u&&l.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];s.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Ta)*Ta,y:Math.round(o(n,t)/Ta)*Ta,i:t}})}var r=Ar,u=Nr,i=r,o=u,a=cl;return n?t(n):(t.links=function(n){return iu(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return iu(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(Ir),c=-1,l=a.length,s=a[l-1].edge,f=s.l===o?s.r:s.l;++c=l,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=su()),f?u=l:a=l,h?o=s:c=s,i(n,t,e,r,u,o,a,c)}var s,f,h,g,p,v,d,m,y,M=kt(a),x=kt(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=su();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){fu(n,k,v,d,m,y)},k.find=function(n){return hu(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=hl.get(e)||fl,r=gl.get(r)||Et,Mu(r(e.apply(null,ea.call(arguments,1))))},ta.interpolateHcl=Lu,ta.interpolateHsl=Tu,ta.interpolateLab=Ru,ta.interpolateRound=Du,ta.transform=function(n){var t=ua.createElementNS(ta.ns.prefix.svg,"g");return(ta.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Pu(e?e.matrix:pl)})(n)},Pu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var pl={a:1,b:0,c:0,d:1,e:0,f:0};ta.interpolateTransform=Hu,ta.layout={},ta.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/d){if(p>c){var l=t.charge/c;n.px-=i*l,n.py-=o*l}return!0}if(t.point&&c&&p>c){var l=t.pointCharge/c;n.px-=i*l,n.py-=o*l}}return!t.charge}}function t(n){n.px=ta.event.x,n.py=ta.event.y,a.resume()}var e,r,u,i,o,a={},c=ta.dispatch("start","tick","end"),l=[1,1],s=.9,f=vl,h=dl,g=-30,p=ml,v=.1,d=.64,m=[],y=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,p,d,M,x,b=m.length,_=y.length;for(e=0;_>e;++e)a=y[e],f=a.source,h=a.target,M=h.x-f.x,x=h.y-f.y,(p=M*M+x*x)&&(p=r*i[e]*((p=Math.sqrt(p))-u[e])/p,M*=p,x*=p,h.x-=M*(d=f.weight/(h.weight+f.weight)),h.y-=x*d,f.x+=M*(d=1-d),f.y+=x*d);if((d=r*v)&&(M=l[0]/2,x=l[1]/2,e=-1,d))for(;++e0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),ta.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;l>a;++a){var u=y[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,l=o.length;++at;++t)(r=m[t]).index=t,r.weight=0;for(t=0;s>t;++t)r=y[t],"number"==typeof r.source&&(r.source=m[r.source]),"number"==typeof r.target&&(r.target=m[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=m[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;s>t;++t)u[t]=+f.call(this,y[t],t);else for(t=0;s>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;s>t;++t)i[t]=+h.call(this,y[t],t);else for(t=0;s>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,m[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=ta.behavior.drag().origin(Et).on("dragstart.force",Xu).on("drag.force",t).on("dragend.force",$u)),arguments.length?(this.on("mouseover.force",Bu).on("mouseout.force",Wu).call(e),void 0):e},ta.rebind(a,c,"on")};var vl=20,dl=1,ml=1/0;ta.layout.hierarchy=function(){function n(u){var i,o=[u],a=[];for(u.depth=0;null!=(i=o.pop());)if(a.push(i),(l=e.call(n,i,i.depth))&&(c=l.length)){for(var c,l,s;--c>=0;)o.push(s=l[c]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=l}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return Qu(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),a}var t=ei,e=ni,r=ti;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(Ku(t,function(n){n.children&&(n.value=0)}),Qu(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ta.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,l=-1;for(r=t.value?r/t.value:0;++lf?-1:1),p=(f-c*g)/ta.sum(l),v=ta.range(c),d=[];return null!=e&&v.sort(e===yl?function(n,t){return l[t]-l[n]}:function(n,t){return e(o[n],o[t])}),v.forEach(function(n){d[n]={data:o[n],value:a=l[n],startAngle:s,endAngle:s+=a*p+g,padAngle:h}}),d}var t=Number,e=yl,r=0,u=Pa,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var yl={};ta.layout.stack=function(){function n(a,c){if(!(h=a.length))return a;var l=a.map(function(e,r){return t.call(n,e,r)}),s=l.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,s,c);l=ta.permute(l,f),s=ta.permute(s,f);var h,g,p,v,d=r.call(n,s,c),m=l[0].length;for(p=0;m>p;++p)for(u.call(n,l[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,l[g][p],v+=s[g-1][p][1],s[g][p][1]);return a}var t=Et,e=ai,r=ci,u=oi,i=ui,o=ii;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:Ml.get(t)||ai,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:xl.get(t)||ci,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var Ml=ta.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(li),i=n.map(si),o=ta.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,l=[],s=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],l.push(e)):(c+=i[e],s.push(e));return s.reverse().concat(l)},reverse:function(n){return ta.range(n.length).reverse()},"default":ai}),xl=ta.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,l,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=l=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];s>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,l>c&&(l=c)}for(e=0;h>e;++e)g[e]-=l;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:ci});ta.layout.histogram=function(){function n(n,i){for(var o,a,c=[],l=n.map(e,this),s=r.call(this,l,i),f=u.call(this,s,l,i),i=-1,h=l.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&a<=s[1]&&(o=c[ta.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=pi,u=hi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=kt(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return gi(n,t)}:kt(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ta.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],l=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Qu(a,function(n){n.r=+s(n.value)}),Qu(a,Mi),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/l))/2;Qu(a,function(n){n.r+=f}),Qu(a,Mi),Qu(a,function(n){n.r-=f})}return _i(a,c/2,l/2,t?1:1/Math.max(2*a.r/c,2*a.r/l)),o}var t,e=ta.layout.hierarchy().sort(vi),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},Gu(n,e)},ta.layout.tree=function(){function n(n,u){var s=o.call(this,n,u),f=s[0],h=t(f);if(Qu(h,e),h.parent.m=-h.z,Ku(h,r),l)Ku(f,i);else{var g=f,p=f,v=f;Ku(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=a(g,p)/2-g.x,m=c[0]/(p.x+a(p,g)/2+d),y=c[1]/(v.depth||1);Ku(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,o=0,a=i.length;a>o;++o)r.push((i[o]=u={_:i[o],parent:t,children:(u=i[o].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Ni(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+a(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,o=t,c=u.parent.children[0],l=u.m,s=i.m,f=o.m,h=c.m;o=Ei(o),u=ki(u),o&&u;)c=ki(c),i=Ei(i),i.a=n,r=o.z+f-u.z-l+a(o._,u._),r>0&&(Ai(Ci(o,n,e),n,r),l+=r,s+=r),f+=o.m,l+=u.m,h+=c.m,s+=i.m;o&&!Ei(i)&&(i.t=o,i.m+=f-s),u&&!ki(c)&&(c.t=u,c.m+=l-h,e=n)}return e}function i(n){n.x*=c[0],n.y=n.depth*c[1]}var o=ta.layout.hierarchy().sort(null).value(null),a=Si,c=[1,1],l=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(l=null==(c=t)?i:null,n):l?null:c},n.nodeSize=function(t){return arguments.length?(l=null==(c=t)?null:i,n):l?c:null},Gu(n,o)},ta.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],l=0;Qu(c,function(n){var t=n.children;t&&t.length?(n.x=qi(t),n.y=zi(t)):(n.x=o?l+=e(n,o):0,n.y=0,o=n)});var s=Li(c),f=Ti(c),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return Qu(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=ta.layout.hierarchy().sort(null).value(null),e=Si,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Gu(n,t)},ta.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,l=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?l.dx:"dice"===g?l.dy:"slice-dice"===g?1&e.depth?l.dy:l.dx:Math.min(l.dx,l.dy);for(n(h,l.dx*l.dy/e.value),s.area=0;(c=h.length)>0;)s.push(o=h[c-1]),s.area+=o.area,"squarify"!==g||(a=r(s,v))<=p?(h.pop(),p=a):(s.area-=s.pop().area,u(s,v,l,!1),v=Math.min(l.dx,l.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,l,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++oe&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,l=e.y,s=t?c(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=ta.random.normal.apply(ta,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ta.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ta.scale={};var bl={floor:Et,ceil:Et};ta.scale.linear=function(){return Yi([0,1],[0,1],mu,!1)};var _l={s:1,g:1,p:1,r:1,e:1};ta.scale.log=function(){return Ji(ta.scale.linear().domain([0,1]),10,!0,[1,10])};var wl=ta.format(".0e"),Sl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ta.scale.pow=function(){return Gi(ta.scale.linear(),1,[0,1])},ta.scale.sqrt=function(){return ta.scale.pow().exponent(.5)},ta.scale.ordinal=function(){return Qi([],{t:"range",a:[[]]})},ta.scale.category10=function(){return ta.scale.ordinal().range(kl)},ta.scale.category20=function(){return ta.scale.ordinal().range(El)},ta.scale.category20b=function(){return ta.scale.ordinal().range(Al)},ta.scale.category20c=function(){return ta.scale.ordinal().range(Nl)};var kl=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(yt),El=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(yt),Al=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(yt),Nl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(yt);ta.scale.quantile=function(){return no([],[])},ta.scale.quantize=function(){return to(0,1,[0,1])},ta.scale.threshold=function(){return eo([.5],[0,1])},ta.scale.identity=function(){return ro([0,1])},ta.svg={},ta.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),l=Math.max(0,+r.apply(this,arguments)),s=o.apply(this,arguments)-ja,f=a.apply(this,arguments)-ja,h=Math.abs(f-s),g=s>f?0:1;if(n>l&&(p=l,l=n,n=p),h>=Ua)return t(l,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,E=0,A=0,N=[];if((m=(+c.apply(this,arguments)||0)/2)&&(d=i===Cl?Math.sqrt(n*n+l*l):+i.apply(this,arguments),g||(A*=-1),l&&(A=nt(d/l*Math.sin(m))),n&&(E=nt(d/n*Math.sin(m)))),l){y=l*Math.cos(s+A),M=l*Math.sin(s+A),x=l*Math.cos(f-A),b=l*Math.sin(f-A);var C=Math.abs(f-s-2*A)<=Da?0:1;if(A&&so(y,M,x,b)===g^C){var z=(s+f)/2;y=l*Math.cos(z),M=l*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-E),w=n*Math.sin(f-E),S=n*Math.cos(s+E),k=n*Math.sin(s+E);var q=Math.abs(s-f+2*E)<=Da?0:1;if(E&&so(_,w,S,k)===1-g^q){var L=(s+f)/2;_=n*Math.cos(L),w=n*Math.sin(L),S=k=null}}else _=w=0;if((p=Math.min(Math.abs(l-n)/2,+u.apply(this,arguments)))>.001){v=l>n^g?0:1;var T=null==S?[_,w]:null==x?[y,M]:Lr([y,M],[S,k],[x,b],[_,w]),R=y-T[0],D=M-T[1],P=x-T[0],U=b-T[1],j=1/Math.sin(Math.acos((R*P+D*U)/(Math.sqrt(R*R+D*D)*Math.sqrt(P*P+U*U)))/2),F=Math.sqrt(T[0]*T[0]+T[1]*T[1]);if(null!=x){var H=Math.min(p,(l-F)/(j+1)),O=fo(null==S?[_,w]:[S,k],[y,M],l,H,g),Y=fo([x,b],[_,w],l,H,g);p===H?N.push("M",O[0],"A",H,",",H," 0 0,",v," ",O[1],"A",l,",",l," 0 ",1-g^so(O[1][0],O[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",H,",",H," 0 0,",v," ",Y[0]):N.push("M",O[0],"A",H,",",H," 0 1,",v," ",Y[0])}else N.push("M",y,",",M);if(null!=S){var I=Math.min(p,(n-F)/(j-1)),Z=fo([y,M],[S,k],n,-I,g),V=fo([_,w],null==x?[y,M]:[x,b],n,-I,g);p===I?N.push("L",V[0],"A",I,",",I," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^so(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",I,",",I," 0 0,",v," ",Z[0]):N.push("L",V[0],"A",I,",",I," 0 0,",v," ",Z[0])}else N.push("L",_,",",w)}else N.push("M",y,",",M),null!=x&&N.push("A",l,",",l," 0 ",C,",",g," ",x,",",b),N.push("L",_,",",w),null!=S&&N.push("A",n,",",n," 0 ",q,",",1-g," ",S,",",k);return N.push("Z"),N.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=io,r=oo,u=uo,i=Cl,o=ao,a=co,c=lo;return n.innerRadius=function(t){return arguments.length?(e=kt(t),n):e},n.outerRadius=function(t){return arguments.length?(r=kt(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=kt(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==Cl?Cl:kt(t),n):i},n.startAngle=function(t){return arguments.length?(o=kt(t),n):o},n.endAngle=function(t){return arguments.length?(a=kt(t),n):a},n.padAngle=function(t){return arguments.length?(c=kt(t),n):c},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-ja;return[Math.cos(t)*n,Math.sin(t)*n]},n};var Cl="auto";ta.svg.line=function(){return ho(Et)};var zl=ta.map({linear:go,"linear-closed":po,step:vo,"step-before":mo,"step-after":yo,basis:So,"basis-open":ko,"basis-closed":Eo,bundle:Ao,cardinal:bo,"cardinal-open":Mo,"cardinal-closed":xo,monotone:To});zl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var ql=[0,2/3,1/3,0],Ll=[0,1/3,2/3,0],Tl=[0,1/6,2/3,1/6];ta.svg.line.radial=function(){var n=ho(Ro);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},mo.reverse=yo,yo.reverse=mo,ta.svg.area=function(){return Do(Et)},ta.svg.area.radial=function(){var n=Do(Ro);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ta.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),l=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,l)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,l.r,l.p0)+r(l.r,l.p1,l.a1-l.a0)+u(l.r,l.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)-ja,s=l.call(n,u,r)-ja;return{r:i,a0:o,a1:s,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Da)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=mr,o=yr,a=Po,c=ao,l=co;return n.radius=function(t){return arguments.length?(a=kt(t),n):a},n.source=function(t){return arguments.length?(i=kt(t),n):i},n.target=function(t){return arguments.length?(o=kt(t),n):o},n.startAngle=function(t){return arguments.length?(c=kt(t),n):c},n.endAngle=function(t){return arguments.length?(l=kt(t),n):l},n},ta.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=mr,e=yr,r=Uo;return n.source=function(e){return arguments.length?(t=kt(e),n):t},n.target=function(t){return arguments.length?(e=kt(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ta.svg.diagonal.radial=function(){var n=ta.svg.diagonal(),t=Uo,e=n.projection;return n.projection=function(n){return arguments.length?e(jo(t=n)):t},n},ta.svg.symbol=function(){function n(n,r){return(Rl.get(t.call(this,n,r))||Oo)(e.call(this,n,r))}var t=Ho,e=Fo;return n.type=function(e){return arguments.length?(t=kt(e),n):t},n.size=function(t){return arguments.length?(e=kt(t),n):e},n};var Rl=ta.map({circle:Oo,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Pl)),e=t*Pl;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Dl),e=t*Dl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Dl),e=t*Dl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ta.svg.symbolTypes=Rl.keys();var Dl=Math.sqrt(3),Pl=Math.tan(30*Fa);ka.transition=function(n){for(var t,e,r=Ul||++Ol,u=Xo(n),i=[],o=jl||{time:Date.now(),ease:Su,delay:0,duration:250},a=-1,c=this.length;++ai;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return Io(u,this.namespace,this.id)},Hl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):H(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Hl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Hu:mu,a=ta.ns.qualify(n);return Zo(this,"attr."+n,t,a.local?i:u)},Hl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=ta.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Hl.style=function(n,t,e){function r(){this.style.removeProperty(n)}function u(t){return null==t?r:(t+="",function(){var r,u=oa.getComputedStyle(this,null).getPropertyValue(n);return u!==t&&(r=mu(u,t),function(t){this.style.setProperty(n,r(t),e)})})}var i=arguments.length;if(3>i){if("string"!=typeof n){2>i&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return Zo(this,"style."+n,t,u)},Hl.styleTween=function(n,t,e){function r(r,u){var i=t.call(this,r,u,oa.getComputedStyle(this,null).getPropertyValue(n));return i&&function(t){this.style.setProperty(n,i(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Hl.text=function(n){return Zo(this,"text",n,Vo)},Hl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Hl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ta.ease.apply(ta,arguments)),H(this,function(r){r[e][t].ease=n}))},Hl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:H(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Hl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:H(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Hl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=jl,i=Ul;try{Ul=e,H(this,function(t,u,i){jl=t[r][e],n.call(t,t.__data__,u,i)})}finally{jl=u,Ul=i}}else H(this,function(u){var i=u[r][e];(i.event||(i.event=ta.dispatch("start","end","interrupt"))).on(n,t)});return this},Hl.transition=function(){for(var n,t,e,r,u=this.id,i=++Ol,o=this.namespace,a=[],c=0,l=this.length;l>c;c++){a.push(n=[]);for(var t=this[c],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[o][u],$o(e,s,o,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Io(a,o,i)},ta.svg.axis=function(){function n(n){n.each(function(){var n,l=ta.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):Et:t,p=l.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Ta),d=ta.transition(p.exit()).style("opacity",Ta).remove(),m=ta.transition(p.order()).style("opacity",1),y=Math.max(u,0)+o,M=Ui(f),x=l.selectAll(".domain").data([0]),b=(x.enter().append("path").attr("class","domain"),ta.transition(x));v.append("line"),v.append("text");var _,w,S,k,E=v.select("line"),A=m.select("line"),N=p.select("text").text(g),C=v.select("text"),z=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=Bo,_="x",S="y",w="x2",k="y2",N.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),b.attr("d","M"+M[0]+","+q*i+"V0H"+M[1]+"V"+q*i)):(n=Wo,_="y",S="x",w="y2",k="x2",N.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),b.attr("d","M"+q*i+","+M[0]+"H0V"+M[1]+"H"+q*i)),E.attr(k,q*u),C.attr(S,q*y),A.attr(w,0).attr(k,q*u),z.attr(_,0).attr(S,q*y),f.rangeBand){var L=f,T=L.rangeBand()/2;s=f=function(n){return L(n)+T}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=ta.scale.linear(),r=Yl,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Il?t+"":Yl,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Yl="bottom",Il={top:1,right:1,bottom:1,left:1};ta.svg.brush=function(){function n(i){i.each(function(){var i=ta.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=i.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),i.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=i.selectAll(".resize").data(p,Et);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Zl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var s,f=ta.transition(i),h=ta.transition(o);c&&(s=Ui(c),h.attr("x",s[0]).attr("width",s[1]-s[0]),e(f)),l&&(s=Ui(l),h.attr("y",s[0]).attr("height",s[1]-s[0]),r(f)),t(f)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+f[+/^s/.test(n)]+")"})}function e(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function r(n){n.select(".extent").attr("y",f[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",f[1]-f[0])}function u(){function u(){32==ta.event.keyCode&&(N||(y=null,z[0]-=s[1],z[1]-=f[1],N=2),b())}function p(){32==ta.event.keyCode&&2==N&&(z[0]+=s[1],z[1]+=f[1],N=0,b())}function v(){var n=ta.mouse(x),u=!1;M&&(n[0]+=M[0],n[1]+=M[1]),N||(ta.event.altKey?(y||(y=[(s[0]+s[1])/2,(f[0]+f[1])/2]),z[0]=s[+(n[0]p?(u=r,r=p):u=p),v[0]!=r||v[1]!=u?(e?o=null:i=null,v[0]=r,v[1]=u,!0):void 0}function m(){v(),S.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ta.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),C(),w({type:"brushend"})}var y,M,x=this,_=ta.select(ta.event.target),w=a.of(x,arguments),S=ta.select(x),k=_.datum(),E=!/^(n|s)$/.test(k)&&c,A=!/^(e|w)$/.test(k)&&l,N=_.classed("extent"),C=X(),z=ta.mouse(x),q=ta.select(oa).on("keydown.brush",u).on("keyup.brush",p);if(ta.event.changedTouches?q.on("touchmove.brush",v).on("touchend.brush",m):q.on("mousemove.brush",v).on("mouseup.brush",m),S.interrupt().selectAll("*").interrupt(),N)z[0]=s[0]-z[0],z[1]=f[0]-z[1];else if(k){var L=+/w$/.test(k),T=+/^n/.test(k);M=[s[1-L]-z[0],f[1-T]-z[1]],z[0]=s[L],z[1]=f[T]}else ta.event.altKey&&(y=z.slice());S.style("pointer-events","none").selectAll(".resize").style("display",null),ta.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),v()}var i,o,a=w(n,"brushstart","brush","brushend"),c=null,l=null,s=[0,0],f=[0,0],h=!0,g=!0,p=Vl[0];return n.event=function(n){n.each(function(){var n=a.of(this,arguments),t={x:s,y:f,i:i,j:o},e=this.__chart__||t;this.__chart__=t,Ul?ta.select(this).transition().each("start.brush",function(){i=e.i,o=e.j,s=e.x,f=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=yu(s,t.x),r=yu(f,t.y);return i=o=null,function(u){s=t.x=e(u),f=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){i=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,p=Vl[!c<<1|!l],n):c},n.y=function(t){return arguments.length?(l=t,p=Vl[!c<<1|!l],n):l},n.clamp=function(t){return arguments.length?(c&&l?(h=!!t[0],g=!!t[1]):c?h=!!t:l&&(g=!!t),n):c&&l?[h,g]:c?h:l?g:null},n.extent=function(t){var e,r,u,a,h;return arguments.length?(c&&(e=t[0],r=t[1],l&&(e=e[0],r=r[0]),i=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(h=e,e=r,r=h),(e!=s[0]||r!=s[1])&&(s=[e,r])),l&&(u=t[0],a=t[1],c&&(u=u[1],a=a[1]),o=[u,a],l.invert&&(u=l(u),a=l(a)),u>a&&(h=u,u=a,a=h),(u!=f[0]||a!=f[1])&&(f=[u,a])),n):(c&&(i?(e=i[0],r=i[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(h=e,e=r,r=h))),l&&(o?(u=o[0],a=o[1]):(u=f[0],a=f[1],l.invert&&(u=l.invert(u),a=l.invert(a)),u>a&&(h=u,u=a,a=h))),c&&l?[[e,u],[r,a]]:c?[e,r]:l&&[u,a])},n.clear=function(){return n.empty()||(s=[0,0],f=[0,0],i=o=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!l&&f[0]==f[1]},ta.rebind(n,a,"on")};var Zl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Vl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Xl=fc.format=mc.timeFormat,$l=Xl.utc,Bl=$l("%Y-%m-%dT%H:%M:%S.%LZ");Xl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Jo:Bl,Jo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Jo.toString=Bl.toString,fc.second=Ft(function(n){return new hc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),fc.seconds=fc.second.range,fc.seconds.utc=fc.second.utc.range,fc.minute=Ft(function(n){return new hc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),fc.minutes=fc.minute.range,fc.minutes.utc=fc.minute.utc.range,fc.hour=Ft(function(n){var t=n.getTimezoneOffset()/60;return new hc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),fc.hours=fc.hour.range,fc.hours.utc=fc.hour.utc.range,fc.month=Ft(function(n){return n=fc.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),fc.months=fc.month.range,fc.months.utc=fc.month.utc.range;var Wl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Jl=[[fc.second,1],[fc.second,5],[fc.second,15],[fc.second,30],[fc.minute,1],[fc.minute,5],[fc.minute,15],[fc.minute,30],[fc.hour,1],[fc.hour,3],[fc.hour,6],[fc.hour,12],[fc.day,1],[fc.day,2],[fc.week,1],[fc.month,1],[fc.month,3],[fc.year,1]],Gl=Xl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",Ne]]),Kl={range:function(n,t,e){return ta.range(Math.ceil(n/e)*e,+t,e).map(Ko)},floor:Et,ceil:Et};Jl.year=fc.year,fc.scale=function(){return Go(ta.scale.linear(),Jl,Gl)};var Ql=Jl.map(function(n){return[n[0].utc,n[1]]}),ns=$l.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",Ne]]);Ql.year=fc.year.utc,fc.scale.utc=function(){return Go(ta.scale.linear(),Ql,ns)},ta.text=At(function(n){return n.responseText}),ta.json=function(n,t){return Nt(n,"application/json",Qo,t)},ta.html=function(n,t){return Nt(n,"text/html",na,t)},ta.xml=At(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(ta):"object"==typeof module&&module.exports&&(module.exports=ta),this.d3=ta}(); \ No newline at end of file diff --git a/awx/ui/static/lib/d3js/.bower.json b/awx/ui/static/lib/d3js/.bower.json deleted file mode 100644 index 6e2680e72d..0000000000 --- a/awx/ui/static/lib/d3js/.bower.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "d3js", - "homepage": "https://github.com/henrytao-me/d3js", - "_release": "2745db14e3", - "_resolution": { - "type": "branch", - "branch": "master", - "commit": "2745db14e37540fc5a9c4ac3b44152bfd7f92e9a" - }, - "_source": "git://github.com/henrytao-me/d3js.git", - "_target": "*", - "_originalSource": "d3js", - "_direct": true -} \ No newline at end of file diff --git a/awx/ui/static/lib/d3js/.gitignore b/awx/ui/static/lib/d3js/.gitignore deleted file mode 100644 index a72b52ebe8..0000000000 --- a/awx/ui/static/lib/d3js/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz - -pids -logs -results - -npm-debug.log -node_modules diff --git a/awx/ui/static/lib/d3js/LICENSE b/awx/ui/static/lib/d3js/LICENSE deleted file mode 100644 index 63e9564f3a..0000000000 --- a/awx/ui/static/lib/d3js/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Henry Tao - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/awx/ui/static/lib/d3js/README.md b/awx/ui/static/lib/d3js/README.md deleted file mode 100644 index 9ad31ded1c..0000000000 --- a/awx/ui/static/lib/d3js/README.md +++ /dev/null @@ -1,2 +0,0 @@ -d3js -==== diff --git a/awx/ui/static/lib/d3js/build/d3.v3.min.js b/awx/ui/static/lib/d3js/build/d3.v3.min.js deleted file mode 100755 index d30fc08d17..0000000000 --- a/awx/ui/static/lib/d3js/build/d3.v3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -d3=function(){function n(n){return null!=n&&!isNaN(n)}function t(n){return n.length}function e(n){for(var t=1;n*t%1;)t*=10;return t}function r(n,t){try{for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}catch(r){n.prototype=t}}function u(){}function i(){}function o(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function a(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.substring(1);for(var e=0,r=la.length;r>e;++e){var u=la[e]+t;if(u in n)return u}}function c(){}function s(){}function l(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function N(n){return ha(n,xa),n}function L(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t0&&(n=n.substring(0,a));var l=_a.get(n);return l&&(n=l,s=R),a?t?u:r:t?c:i}function z(n,t){return function(e){var r=$o.event;$o.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{$o.event=r}}}function R(n,t){var e=z(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function D(){var n=".dragsuppress-"+ ++wa,t="click"+n,e=$o.select(Ko).on("touchmove"+n,f).on("dragstart"+n,f).on("selectstart"+n,f);if(ba){var r=Go.style,u=r[ba];r[ba]="none"}return function(i){function o(){e.on(t,null)}e.on(n,null),ba&&(r[ba]=u),i&&(e.on(t,function(){f(),o()},!0),setTimeout(o,0))}}function P(n,t){t.changedTouches&&(t=t.changedTouches[0]);var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>Sa&&(Ko.scrollX||Ko.scrollY)){e=$o.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var u=e[0][0].getScreenCTM();Sa=!(u.f||u.e),e.remove()}return Sa?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var i=n.getBoundingClientRect();return[t.clientX-i.left-n.clientLeft,t.clientY-i.top-n.clientTop]}function U(n){return n>0?1:0>n?-1:0}function j(n){return n>1?0:-1>n?ka:Math.acos(n)}function H(n){return n>1?Aa:-1>n?-Aa:Math.asin(n)}function F(n){return((n=Math.exp(n))-1/n)/2}function O(n){return((n=Math.exp(n))+1/n)/2}function Y(n){return((n=Math.exp(2*n))-1)/(n+1)}function I(n){return(n=Math.sin(n/2))*n}function Z(){}function V(n,t,e){return new X(n,t,e)}function X(n,t,e){this.h=n,this.s=t,this.l=e}function $(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,ot(u(n+120),u(n),u(n-120))}function B(n,t,e){return new W(n,t,e)}function W(n,t,e){this.h=n,this.c=t,this.l=e}function J(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),G(e,Math.cos(n*=La)*t,Math.sin(n)*t)}function G(n,t,e){return new K(n,t,e)}function K(n,t,e){this.l=n,this.a=t,this.b=e}function Q(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=tt(u)*Oa,r=tt(r)*Ya,i=tt(i)*Ia,ot(rt(3.2404542*u-1.5371385*r-.4985314*i),rt(-.969266*u+1.8760108*r+.041556*i),rt(.0556434*u-.2040259*r+1.0572252*i))}function nt(n,t,e){return n>0?B(Math.atan2(e,t)*Ta,Math.sqrt(t*t+e*e),n):B(0/0,0/0,n)}function tt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function et(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function rt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function ut(n){return ot(n>>16,255&n>>8,255&n)}function it(n){return ut(n)+""}function ot(n,t,e){return new at(n,t,e)}function at(n,t,e){this.r=n,this.g=t,this.b=e}function ct(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function st(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(gt(u[0]),gt(u[1]),gt(u[2]))}return(i=Xa.get(n))?t(i.r,i.g,i.b):(null!=n&&"#"===n.charAt(0)&&(4===n.length?(o=n.charAt(1),o+=o,a=n.charAt(2),a+=a,c=n.charAt(3),c+=c):7===n.length&&(o=n.substring(1,3),a=n.substring(3,5),c=n.substring(5,7)),o=parseInt(o,16),a=parseInt(a,16),c=parseInt(c,16)),t(o,a,c))}function lt(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),V(r,u,c)}function ft(n,t,e){n=ht(n),t=ht(t),e=ht(e);var r=et((.4124564*n+.3575761*t+.1804375*e)/Oa),u=et((.2126729*n+.7151522*t+.072175*e)/Ya),i=et((.0193339*n+.119192*t+.9503041*e)/Ia);return G(116*u-16,500*(r-u),200*(u-i))}function ht(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function gt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function pt(n){return"function"==typeof n?n:function(){return n}}function vt(n){return n}function dt(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),mt(t,e,n,r)}}function mt(n,t,e,r){function u(){var n,t=c.status;if(!t&&c.responseText||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return o.error.call(i,r),void 0}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=$o.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,s=null;return!Ko.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=$o.event;$o.event=n;try{o.progress.call(i,c)}finally{$o.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(s=n,i):s},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(Wo(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var l in a)c.setRequestHeader(l,a[l]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=s&&(c.responseType=s),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},$o.rebind(i,o,"on"),null==r?i:i.get(yt(r))}function yt(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function xt(){var n=Mt(),t=_t()-n;t>24?(isFinite(t)&&(clearTimeout(Ja),Ja=setTimeout(xt,t)),Wa=0):(Wa=1,Ka(xt))}function Mt(){var n=Date.now();for(Ga=$a;Ga;)n>=Ga.t&&(Ga.f=Ga.c(n-Ga.t)),Ga=Ga.n;return n}function _t(){for(var n,t=$a,e=1/0;t;)t.f?t=n?n.n=t.n:$a=t.n:(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function wt(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function St(n){return n+""}function kt(){}function Et(n,t,e){var r=e.s=n+t,u=r-n,i=r-u;e.t=n-i+(t-u)}function At(n,t){n&&lc.hasOwnProperty(n.type)&&lc[n.type](n,t)}function Ct(n,t,e){var r,u=-1,i=n.length-e;for(t.lineStart();++ua;++a)u.point((e=n[a])[0],e[1]);return u.lineEnd(),void 0}var c=new $t(e,n,null,!0),s=new $t(e,null,c,!1);c.o=s,i.push(c),o.push(s),c=new $t(r,n,null,!1),s=new $t(r,null,c,!0),c.o=s,i.push(c),o.push(s)}}),o.sort(t),Xt(i),Xt(o),i.length){for(var a=0,c=e,s=o.length;s>a;++a)o[a].e=c=!c;for(var l,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;l=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,s=l.length;s>a;++a)u.point((f=l[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){l=g.p.z;for(var a=l.length-1;a>=0;--a)u.point((f=l[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,l=g.z,p=!p}while(!g.v);u.lineEnd()}}}function Xt(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Wt))}}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:s,polygonStart:function(){y.point=l,y.lineStart=f,y.lineEnd=h,g=[],p=[],i.polygonStart()},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=s,g=$o.merge(g);var n=Kt(m,p);g.length?Vt(g,Gt,n,e,i):n&&(i.lineStart(),e(null,null,1,i),i.lineEnd()),i.polygonEnd(),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},x=Jt(),M=t(x);return y}}function Wt(n){return n.length>1}function Jt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:c,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Gt(n,t){return((n=n.x)[0]<0?n[1]-Aa-Ca:Aa-n[1])-((t=t.x)[0]<0?t[1]-Aa-Ca:Aa-t[1])}function Kt(n,t){var e=n[0],r=n[1],u=[Math.sin(e),-Math.cos(e),0],i=0,o=0;hc.reset();for(var a=0,c=t.length;c>a;++a){var s=t[a],l=s.length;if(l)for(var f=s[0],h=f[0],g=f[1]/2+ka/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===l&&(d=0),n=s[d];var m=n[0],y=n[1]/2+ka/4,x=Math.sin(y),M=Math.cos(y),_=m-h,b=aa(_)>ka,w=p*x;if(hc.add(Math.atan2(w*Math.sin(_),v*M+w*Math.cos(_))),i+=b?_+(_>=0?Ea:-Ea):_,b^h>=e^m>=e){var S=zt(Tt(f),Tt(n));Pt(S);var k=zt(u,S);Pt(k);var E=(b^_>=0?-1:1)*H(k[2]);(r>E||r===E&&(S[0]||S[1]))&&(o+=b^_>=0?1:-1)}if(!d++)break;h=m,p=x,v=M,f=n}}return(-Ca>i||Ca>i&&0>hc)^1&o}function Qt(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?ka:-ka,c=aa(i-e);aa(c-ka)0?Aa:-Aa),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=ka&&(aa(e-u)Ca?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function te(n,t,e,r){var u;if(null==n)u=e*Aa,r.point(-ka,u),r.point(0,u),r.point(ka,u),r.point(ka,0),r.point(ka,-u),r.point(0,-u),r.point(-ka,-u),r.point(-ka,0),r.point(-ka,u);else if(aa(n[0]-t[0])>Ca){var i=n[0]i}function e(n){var e,i,c,s,l;return{lineStart:function(){s=c=!1,l=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?ka:-ka),h):0;if(!e&&(s=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(jt(e,g)||jt(p,g))&&(p[0]+=Ca,p[1]+=Ca,v=t(p[0],p[1]))),v!==c)l=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(l=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&jt(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return l|(s&&c)<<1}}}function r(n,t,e){var r=Tt(n),u=Tt(t),o=[1,0,0],a=zt(r,u),c=qt(a,a),s=a[0],l=c-s*s;if(!l)return!e&&n;var f=i*c/l,h=-i*s/l,g=zt(o,a),p=Dt(o,f),v=Dt(a,h);Rt(p,v);var d=g,m=qt(p,d),y=qt(d,d),x=m*m-y*(qt(p,p)-1);if(!(0>x)){var M=Math.sqrt(x),_=Dt(d,(-m-M)/y);if(Rt(_,p),_=Ut(_),!e)return _;var b,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(b=w,w=S,S=b);var A=S-w,C=aa(A-ka)A;if(!C&&k>E&&(b=k,k=E,E=b),N?C?k+E>0^_[1]<(aa(_[0]-w)ka^(w<=_[0]&&_[0]<=S)){var L=Dt(d,(-m+M)/y);return Rt(L,p),[_,Ut(L)]}}}function u(t,e){var r=o?n:ka-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=aa(i)>Ca,c=Le(n,6*La);return Bt(t,e,c,o?[0,-n]:[-ka,n-ka])}function re(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,s=o.y,l=a.x,f=a.y,h=0,g=1,p=l-c,v=f-s;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-s,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-s,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:s+h*v}),1>g&&(u.b={x:c+g*p,y:s+g*v}),u}}}}}}function ue(n,t,e,r){function u(r,u){return aa(r[0]-n)0?0:3:aa(r[0]-e)0?2:1:aa(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=m.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=m[u],c=a.length,l=a[0];c>o;++o)i=a[o],l[1]<=r?i[1]>r&&s(l,i,n)>0&&++t:i[1]<=r&&s(l,i,n)<0&&--t,l=i;return 0!==t}function s(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(e[0]-n[0])*(t[1]-n[1])}function l(i,a,c,s){var l=0,f=0;if(null==i||(l=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do s.point(0===l||3===l?n:e,l>1?r:t);while((l=(l+c+4)%4)!==f)}else s.point(a[0],a[1])}function f(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function h(n,t){f(n,t)&&a.point(n,t)}function g(){L.point=v,m&&m.push(y=[]),k=!0,S=!1,b=w=0/0}function p(){d&&(v(x,M),_&&S&&C.rejoin(),d.push(C.buffer())),L.point=h,S&&a.lineEnd()}function v(n,t){n=Math.max(-Ac,Math.min(Ac,n)),t=Math.max(-Ac,Math.min(Ac,t));var e=f(n,t);if(m&&y.push([n,t]),k)x=n,M=t,_=e,k=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&S)a.point(n,t);else{var r={a:{x:b,y:w},b:{x:n,y:t}};N(r)?(S||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),E=!1):e&&(a.lineStart(),a.point(n,t),E=!1)}b=n,w=t,S=e}var d,m,y,x,M,_,b,w,S,k,E,A=a,C=Jt(),N=re(n,t,e,r),L={point:h,lineStart:g,lineEnd:p,polygonStart:function(){a=C,d=[],m=[],E=!0},polygonEnd:function(){a=A,d=$o.merge(d);var t=c([n,r]),e=E&&t,u=d.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),l(null,null,1,a),a.lineEnd()),u&&Vt(d,i,t,l,a),a.polygonEnd()),d=m=y=null}};return L}}function ie(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.invert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function oe(n){var t=0,e=ka/3,r=be(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ka/180,e=n[1]*ka/180):[180*(t/ka),180*(e/ka)]},u}function ae(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,H((i-(n*n+e*e)*u*u)/(2*u))]},e}function ce(){function n(n,t){Nc+=u*n-r*t,r=n,u=t}var t,e,r,u;Rc.point=function(i,o){Rc.point=n,t=r=i,e=u=o},Rc.lineEnd=function(){n(t,e)}}function se(n,t){Lc>n&&(Lc=n),n>qc&&(qc=n),Tc>t&&(Tc=t),t>zc&&(zc=t)}function le(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=fe(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=fe(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function fe(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function he(n,t){dc+=n,mc+=t,++yc}function ge(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);xc+=o*(t+n)/2,Mc+=o*(e+r)/2,_c+=o,he(t=n,e=r)}var t,e;Pc.point=function(r,u){Pc.point=n,he(t=r,e=u)}}function pe(){Pc.point=he}function ve(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);xc+=o*(r+n)/2,Mc+=o*(u+t)/2,_c+=o,o=u*n-r*t,bc+=o*(r+n),wc+=o*(u+t),Sc+=3*o,he(r=n,u=t)}var t,e,r,u;Pc.point=function(i,o){Pc.point=n,he(t=r=i,e=u=o)},Pc.lineEnd=function(){n(t,e)}}function de(n){function t(t,e){n.moveTo(t,e),n.arc(t,e,o,0,Ea)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:c};return a}function me(n){function t(n){return(a?r:e)(n)}function e(t){return Me(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){x=0/0,S.point=i,t.lineStart()}function i(e,r){var i=Tt([e,r]),o=n(e,r);u(x,M,y,_,b,w,x=o[0],M=o[1],y=e,_=i[0],b=i[1],w=i[2],a,t),t.point(x,M)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=s,S.lineEnd=l}function s(n,t){i(f=n,h=t),g=x,p=M,v=_,d=b,m=w,S.point=i}function l(){u(x,M,y,_,b,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,x,M,_,b,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,s,l,f,h,g,p,v,d,m){var y=l-t,x=f-e,M=y*y+x*x;if(M>4*i&&d--){var _=a+g,b=c+p,w=s+v,S=Math.sqrt(_*_+b*b+w*w),k=Math.asin(w/=S),E=aa(aa(w)-1)i||aa((y*L+x*T)/M-.5)>.3||o>a*g+c*p+s*v)&&(u(t,e,r,a,c,s,C,N,E,_/=S,b/=S,w,d,m),m.point(C,N),u(C,N,E,_,b,w,l,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*La),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function ye(n){var t=me(function(t,e){return n([t*Ta,e*Ta])});return function(n){return we(t(n))}}function xe(n){this.stream=n}function Me(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function _e(n){return be(function(){return n})()}function be(n){function t(n){return n=a(n[0]*La,n[1]*La),[n[0]*h+c,s-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(s-n[1])/h),n&&[n[0]*Ta,n[1]*Ta]}function r(){a=ie(o=Ee(m,y,x),i);var n=i(v,d);return c=g-n[0]*h,s=p+n[1]*h,u()}function u(){return l&&(l.valid=!1,l=null),t}var i,o,a,c,s,l,f=me(function(n,t){return n=i(n,t),[n[0]*h+c,s-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,y=0,x=0,M=Ec,_=vt,b=null,w=null;return t.stream=function(n){return l&&(l.valid=!1),l=we(M(o,f(_(n)))),l.valid=!0,l},t.clipAngle=function(n){return arguments.length?(M=null==n?(b=n,Ec):ee((b=+n)*La),u()):b},t.clipExtent=function(n){return arguments.length?(w=n,_=n?ue(n[0][0],n[0][1],n[1][0],n[1][1]):vt,u()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*La,d=n[1]%360*La,r()):[v*Ta,d*Ta]},t.rotate=function(n){return arguments.length?(m=n[0]%360*La,y=n[1]%360*La,x=n.length>2?n[2]%360*La:0,r()):[m*Ta,y*Ta,x*Ta]},$o.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function we(n){return Me(n,function(t,e){n.point(t*La,e*La)})}function Se(n,t){return[n,t]}function ke(n,t){return[n>ka?n-Ea:-ka>n?n+Ea:n,t]}function Ee(n,t,e){return n?t||e?ie(Ce(n),Ne(t,e)):Ce(n):t||e?Ne(t,e):ke}function Ae(n){return function(t,e){return t+=n,[t>ka?t-Ea:-ka>t?t+Ea:t,e]}}function Ce(n){var t=Ae(n);return t.invert=Ae(-n),t}function Ne(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*r+a*u;return[Math.atan2(c*i-l*o,a*r-s*u),H(l*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*i-c*o;return[Math.atan2(c*i+s*o,a*r+l*u),H(l*r-a*u)]},e}function Le(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=Te(e,u),i=Te(e,i),(o>0?i>u:u>i)&&(u+=o*Ea)):(u=n+o*Ea,i=n-.5*c);for(var s,l=u;o>0?l>i:i>l;l-=c)a.point((s=Ut([e,-r*Math.cos(l),-r*Math.sin(l)]))[0],s[1])}}function Te(n,t){var e=Tt(t);e[0]-=n,Pt(e);var r=j(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Ca)%(2*Math.PI)}function qe(n,t,e){var r=$o.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function ze(n,t,e){var r=$o.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Re(n){return n.source}function De(n){return n.target}function Pe(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),s=u*Math.sin(n),l=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(I(r-t)+u*o*I(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*l,u=e*s+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Ta,Math.atan2(o,Math.sqrt(r*r+u*u))*Ta]}:function(){return[n*Ta,t*Ta]};return p.distance=h,p}function Ue(){function n(n,u){var i=Math.sin(u*=La),o=Math.cos(u),a=aa((n*=La)-t),c=Math.cos(a);Uc+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;jc.point=function(u,i){t=u*La,e=Math.sin(i*=La),r=Math.cos(i),jc.point=n},jc.lineEnd=function(){jc.point=jc.lineEnd=c}}function je(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function He(n,t){function e(n,t){var e=aa(aa(t)-Aa)0}function $e(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Be(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],s=e[1],l=t[1]-c,f=r[1]-s,h=(a*(c-s)-f*(u-i))/(f*o-a*l);return[u+h*o,c+h*l]}function We(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Je(){mr(this),this.edge=this.site=this.circle=null}function Ge(n){var t=Jc.pop()||new Je;return t.site=n,t}function Ke(n){cr(n),$c.remove(n),Jc.push(n),mr(n)}function Qe(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Ke(n);for(var c=i;c.circle&&aa(e-c.circle.x)l;++l)s=a[l],c=a[l-1],pr(s.edge,c.site,s.site,u);c=a[0],s=a[f-1],s.edge=hr(c.site,s.site,null,u),ar(c),ar(s)}function nr(n){for(var t,e,r,u,i=n.x,o=n.y,a=$c._;a;)if(r=tr(a,o)-i,r>Ca)a=a.L;else{if(u=i-er(a,o),!(u>Ca)){r>-Ca?(t=a.P,e=a):u>-Ca?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Ge(n);if($c.insert(t,c),t||e){if(t===e)return cr(t),e=Ge(t.site),$c.insert(c,e),c.edge=e.edge=hr(t.site,c.site),ar(t),ar(e),void 0;if(!e)return c.edge=hr(t.site,c.site),void 0;cr(t),cr(e);var s=t.site,l=s.x,f=s.y,h=n.x-l,g=n.y-f,p=e.site,v=p.x-l,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,x=v*v+d*d,M={x:(d*y-g*x)/m+l,y:(h*x-v*y)/m+f};pr(e.edge,s,p,M),c.edge=hr(s,n,null,M),e.edge=hr(n,p,null,M),ar(t),ar(e)}}function tr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,s=c-t;if(!s)return a;var l=a-r,f=1/i-1/s,h=l/s;return f?(-h+Math.sqrt(h*h-2*f*(l*l/(-2*s)-c+s/2+u-i/2)))/f+r:(r+a)/2}function er(n,t){var e=n.N;if(e)return tr(e,t);var r=n.site;return r.y===t?r.x:1/0}function rr(n){this.site=n,this.edges=[]}function ur(n){for(var t,e,r,u,i,o,a,c,s,l,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=Xc,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)l=a[o].end(),r=l.x,u=l.y,s=a[++o%c].start(),t=s.x,e=s.y,(aa(r-t)>Ca||aa(u-e)>Ca)&&(a.splice(o,0,new vr(gr(i.site,l,aa(r-f)Ca?{x:f,y:aa(t-f)Ca?{x:aa(e-p)Ca?{x:h,y:aa(t-h)Ca?{x:aa(e-g)=-Na)){var g=c*c+s*s,p=l*l+f*f,v=(f*g-s*p)/h,d=(c*p-l*g)/h,f=d+a,m=Gc.pop()||new or;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,x=Wc._;x;)if(m.yd||d>=a)return;if(h>p){if(i){if(i.y>=s)return}else i={x:d,y:c};e={x:d,y:s}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=s)return}else i={x:(c-u)/r,y:c};e={x:(s-u)/r,y:s}}else{if(i){if(i.yg){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.xr;++r)if(o=l[r],o.x==e[0]){if(o.i)if(null==s[o.i+1])for(s[o.i-1]+=o.x,s.splice(o.i,1),u=r+1;i>u;++u)l[u].i--;else for(s[o.i-1]+=o.x+s[o.i+1],s.splice(o.i,2),u=r+1;i>u;++u)l[u].i-=2;else if(null==s[o.i+1])s[o.i]=o.x;else for(s[o.i]=o.x+s[o.i+1],s.splice(o.i+1,1),u=r+1;i>u;++u)l[u].i--;l.splice(r,1),i--,r--}else o.x=Lr(parseFloat(e[0]),parseFloat(o.x));for(;i>r;)o=l.pop(),null==s[o.i+1]?s[o.i]=o.x:(s[o.i]=o.x+s[o.i+1],s.splice(o.i+1,1)),i--;return 1===s.length?null==s[0]?(o=l[0].x,function(n){return o(n)+""}):function(){return t}:function(n){for(r=0;i>r;++r)s[(o=l[r]).i]=o.x(n);return s.join("")}}function qr(n,t){for(var e,r=$o.interpolators.length;--r>=0&&!(e=$o.interpolators[r](n,t)););return e}function zr(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(qr(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function Rr(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function Dr(n){return function(t){return 1-n(1-t)}}function Pr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Ur(n){return n*n}function jr(n){return n*n*n}function Hr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Fr(n){return function(t){return Math.pow(t,n)}}function Or(n){return 1-Math.cos(n*Aa)}function Yr(n){return Math.pow(2,10*(n-1))}function Ir(n){return 1-Math.sqrt(1-n*n)}function Zr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ea*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ea/t)}}function Vr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Xr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function $r(n,t){n=$o.hcl(n),t=$o.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return J(e+i*n,r+o*n,u+a*n)+""}}function Br(n,t){n=$o.hsl(n),t=$o.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return $(e+i*n,r+o*n,u+a*n)+""}}function Wr(n,t){n=$o.lab(n),t=$o.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return Q(e+i*n,r+o*n,u+a*n)+""}}function Jr(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Gr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Qr(t),u=Kr(t,e),i=Qr(nu(e,t,-u))||0;t[0]*e[1]180?l+=360:l-s>180&&(s+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:Lr(s,l)})):l&&r.push(r.pop()+"rotate("+l+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:Lr(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:Lr(g[0],p[0])},{i:e-2,x:Lr(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++ie;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function wu(n){return n.reduce(Su,0)}function Su(n,t){return n+t[1]}function ku(n,t){return Eu(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function Eu(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function Au(n){return[$o.min(n),$o.max(n)]}function Cu(n,t){return n.parent==t.parent?1:2}function Nu(n){var t=n.children;return t&&t.length?t[0]:n._tree.thread}function Lu(n){var t,e=n.children;return e&&(t=e.length)?e[t-1]:n._tree.thread}function Tu(n,t){var e=n.children;if(e&&(u=e.length))for(var r,u,i=-1;++i0&&(n=r);return n}function qu(n,t){return n.x-t.x}function zu(n,t){return t.x-n.x}function Ru(n,t){return n.depth-t.depth}function Du(n,t){function e(n,r){var u=n.children;if(u&&(o=u.length))for(var i,o,a=null,c=-1;++c=0;)t=u[i]._tree,t.prelim+=e,t.mod+=e,e+=t.shift+(r+=t.change)}function Uu(n,t,e){n=n._tree,t=t._tree;var r=e/(t.number-n.number);n.change+=r,t.change-=r,t.shift+=e,t.prelim+=e,t.mod+=e}function ju(n,t,e){return n._tree.ancestor.parent==t.parent?n._tree.ancestor:e}function Hu(n,t){return n.value-t.value}function Fu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Ou(n,t){n._pack_next=t,t._pack_prev=n}function Yu(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Iu(n){function t(n){l=Math.min(n.x-n.r,l),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(s=e.length)){var e,r,u,i,o,a,c,s,l=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(Zu),r=e[0],r.x=-r.r,r.y=0,t(r),s>1&&(u=e[1],u.x=u.r,u.y=0,t(u),s>2))for(i=e[2],$u(r,u,i),t(i),Fu(r,i),r._pack_prev=i,Fu(i,u),u=r._pack_next,o=3;s>o;o++){$u(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(Yu(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!Yu(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.ro;o++)i=e[o],i.x-=m,i.y-=y,x=Math.max(x,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=x,e.forEach(Vu)}}function Zu(n){n._pack_next=n._pack_prev=n}function Vu(n){delete n._pack_next,delete n._pack_prev}function Xu(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++iu&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function ni(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function ti(n){return n.rangeExtent?n.rangeExtent():ni(n.range())}function ei(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function ri(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function ui(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:ss}function ii(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?ii:ei,c=r?ru:eu;return o=u(n,t,c,e),a=u(t,n,c,qr),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Jr)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return li(n,t)},i.tickFormat=function(t,e){return fi(n,t,e)},i.nice=function(t){return ci(n,t),u()},i.copy=function(){return oi(n,t,e,r)},u()}function ai(n,t){return $o.rebind(n,t,"range","rangeRound","interpolate","clamp")}function ci(n,t){return ri(n,ui(si(n,t)[2]))}function si(n,t){null==t&&(t=10);var e=ni(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function li(n,t){return $o.range.apply($o,si(n,t))}function fi(n,t,e){var r=si(n,t);return $o.format(e?e.replace(uc,function(n,t,e,u,i,o,a,c,s,l){return[t,e,u,i,o,a,c,s||"."+gi(l,r),l].join("")}):",."+hi(r[2])+"f")}function hi(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function gi(n,t){var e=hi(t[2]);return n in ls?Math.abs(e-hi(Math.max(Math.abs(t[0]),Math.abs(t[1]))))+ +("e"!==n):e-2*("%"===n)}function pi(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=ri(r.map(u),e?Math:hs);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=ni(r),o=[],a=n[0],c=n[1],s=Math.floor(u(a)),l=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(l-s)){if(e){for(;l>s;s++)for(var h=1;f>h;h++)o.push(i(s)*h);o.push(i(s))}else for(o.push(i(s));s++0;h--)o.push(i(s)*h);for(s=0;o[s]c;l--);o=o.slice(s,l)}return o},o.tickFormat=function(n,t){if(!arguments.length)return fs;arguments.length<2?t=fs:"function"!=typeof t&&(t=$o.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return pi(n.copy(),t,e,r)},ai(o,n)}function vi(n,t,e){function r(t){return n(u(t))}var u=di(t),i=di(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return li(e,n)},r.tickFormat=function(n,t){return fi(e,n,t)},r.nice=function(n){return r.domain(ci(e,n))},r.exponent=function(o){return arguments.length?(u=di(t=o),i=di(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return vi(n.copy(),t,e)},ai(r,n)}function di(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function mi(n,t){function e(e){return o[((i.get(e)||"range"===t.t&&i.set(e,n.push(e)))-1)%o.length]}function r(t,e){return $o.range(n.length).map(function(n){return t+e*n})}var i,o,a;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new u;for(var o,a=-1,c=r.length;++ae?[0/0,0/0]:[e>0?u[e-1]:n[0],et?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return xi(n,t,e)},u()}function Mi(n,t){function e(e){return e>=e?t[$o.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return Mi(n,t)},e}function _i(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return li(n,t)},t.tickFormat=function(t,e){return fi(n,t,e)},t.copy=function(){return _i(n)},t}function bi(n){return n.innerRadius}function wi(n){return n.outerRadius}function Si(n){return n.startAngle}function ki(n){return n.endAngle}function Ei(n){function t(t){function o(){s.push("M",i(n(l),a))}for(var c,s=[],l=[],f=-1,h=t.length,g=pt(e),p=pt(r);++f1&&u.push("H",r[0]),u.join("")}function Li(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var s=2;s9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function Xi(n){return n.length<3?Ai(n):n[0]+Di(n,Vi(n))}function $i(n){for(var t,e,r,u=-1,i=n.length;++ue?s():(i.active=e,o.event&&o.event.start.call(n,l,t),o.tween.forEach(function(e,r){(r=r.call(n,l,t))&&v.push(r)}),$o.timer(function(){return p.c=c(r||1)?Zt:c,1},0,a),void 0)}function c(r){if(i.active!==e)return s();for(var u=r/g,a=f(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,l,t),s()):void 0}function s(){return--i.count?delete i[e]:delete n.__transition__,1}var l=n.__data__,f=o.ease,h=o.delay,g=o.duration,p=Ga,v=[];return p.t=h+a,r>=h?u(r-h):(p.c=u,void 0)},0,a)}}function io(n,t){n.attr("transform",function(n){return"translate("+t(n)+",0)"})}function oo(n,t){n.attr("transform",function(n){return"translate(0,"+t(n)+")"})}function ao(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function co(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new Ds(e-1)),1),e}function i(n,e){return t(n=new Ds(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{Ds=ao;var r=new ao;return r._=n,o(r,t,e)}finally{Ds=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=so(n);return c.floor=c,c.round=so(r),c.ceil=so(u),c.offset=so(i),c.range=a,n}function so(n){return function(t,e){try{Ds=ao;var r=new ao;return r._=t,n(r,e)._}finally{Ds=Date}}}function lo(n){function t(t){for(var r,u,i,o=[],a=-1,c=0;++aa;){if(r>=s)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=el[o in nl?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function ho(n){return new RegExp("^(?:"+n.map($o.requote).join("|")+")","i")}function go(n){for(var t=new u,e=-1,r=n.length;++en?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function vo(n,t,e){$s.lastIndex=0;var r=$s.exec(t.substring(e));return r?(n.w=Bs.get(r[0].toLowerCase()),e+r[0].length):-1}function mo(n,t,e){Vs.lastIndex=0;var r=Vs.exec(t.substring(e));return r?(n.w=Xs.get(r[0].toLowerCase()),e+r[0].length):-1}function yo(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function xo(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e));return r?(n.U=+r[0],e+r[0].length):-1}function Mo(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e));return r?(n.W=+r[0],e+r[0].length):-1}function _o(n,t,e){Gs.lastIndex=0;var r=Gs.exec(t.substring(e));return r?(n.m=Ks.get(r[0].toLowerCase()),e+r[0].length):-1}function bo(n,t,e){Ws.lastIndex=0;var r=Ws.exec(t.substring(e));return r?(n.m=Js.get(r[0].toLowerCase()),e+r[0].length):-1}function wo(n,t,e){return fo(n,tl.c.toString(),t,e)}function So(n,t,e){return fo(n,tl.x.toString(),t,e)}function ko(n,t,e){return fo(n,tl.X.toString(),t,e)}function Eo(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function Ao(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+2));return r?(n.y=No(+r[0]),e+r[0].length):-1}function Co(n,t,e){return/^[+-]\d{4}$/.test(t=t.substring(e,e+5))?(n.Z=+t,e+5):-1}function No(n){return n+(n>68?1900:2e3)}function Lo(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function To(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function qo(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function zo(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function Ro(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function Do(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function Po(n,t,e){rl.lastIndex=0;var r=rl.exec(t.substring(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function Uo(n,t,e){var r=ul.get(t.substring(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}function jo(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=~~(aa(t)/60),u=aa(t)%60;return e+po(r,"0",2)+po(u,"0",2)}function Ho(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+1));return r?e+r[0].length:-1}function Fo(n){function t(n){try{Ds=ao;var t=new Ds;return t._=n,e(t)}finally{Ds=Date}}var e=lo(n);return t.parse=function(n){try{Ds=ao;var t=e.parse(n);return t&&t._}finally{Ds=Date}},t.toString=e.toString,t}function Oo(n){return n.toISOString()}function Yo(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=$o.bisect(ol,u);return i==ol.length?[t.year,si(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/ol[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=Io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=ni(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Yo(n.copy(),t,e)},ai(r,n)}function Io(n){return new Date(n)}function Zo(n){return function(t){for(var e=n.length-1,r=n[e];!r[1](t);)r=n[--e];return r[0](t)}}function Vo(n){return JSON.parse(n.responseText)}function Xo(n){var t=Jo.createRange();return t.selectNode(Jo.body),t.createContextualFragment(n.responseText)}var $o={version:"3.3.10"};Date.now||(Date.now=function(){return+new Date});var Bo=[].slice,Wo=function(n){return Bo.call(n)},Jo=document,Go=Jo.documentElement,Ko=window;try{Wo(Go.childNodes)[0].nodeType}catch(Qo){Wo=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}try{Jo.createElement("div").style.setProperty("opacity",0,"")}catch(na){var ta=Ko.Element.prototype,ea=ta.setAttribute,ra=ta.setAttributeNS,ua=Ko.CSSStyleDeclaration.prototype,ia=ua.setProperty;ta.setAttribute=function(n,t){ea.call(this,n,t+"")},ta.setAttributeNS=function(n,t,e){ra.call(this,n,t,e+"")},ua.setProperty=function(n,t,e){ia.call(this,n,t+"",e)}}$o.ascending=function(n,t){return t>n?-1:n>t?1:n>=t?0:0/0},$o.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},$o.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=e);)e=void 0;for(;++ur&&(e=r)}else{for(;++u=e);)e=void 0;for(;++ur&&(e=r)}return e},$o.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=e);)e=void 0;for(;++ue&&(e=r)}else{for(;++u=e);)e=void 0;for(;++ue&&(e=r)}return e},$o.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i=e);)e=u=void 0;for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=e);)e=void 0;for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},$o.sum=function(n,t){var e,r=0,u=n.length,i=-1;if(1===arguments.length)for(;++i1&&(t=t.map(e)),t=t.filter(n),t.length?$o.quantile(t.sort($o.ascending),.5):void 0},$o.bisector=function(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n.call(t,t[i],i)r;){var i=r+u>>>1;er?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},$o.zip=function(){if(!(u=arguments.length))return[];for(var n=-1,e=$o.min(arguments,t),r=new Array(e);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var aa=Math.abs;$o.range=function(n,t,r){if(arguments.length<3&&(r=1,arguments.length<2&&(t=n,n=0)),1/0===(t-n)/r)throw new Error("infinite range");var u,i=[],o=e(aa(r)),a=-1;if(n*=o,t*=o,r*=o,0>r)for(;(u=n+r*++a)>t;)i.push(u/o);else for(;(u=n+r*++a)=o.length)return r?r.call(i,a):e?a.sort(e):a;for(var s,l,f,h,g=-1,p=a.length,v=o[c++],d=new u;++g=o.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,i={},o=[],a=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n($o.map,e,0),0)},i.key=function(n){return o.push(n),i},i.sortKeys=function(n){return a[o.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},$o.set=function(n){var t=new i;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},r(i,{has:function(n){return ca+n in this},add:function(n){return this[ca+n]=!0,n},remove:function(n){return n=ca+n,n in this&&delete this[n]},values:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===sa&&n.call(this,t.substring(1))}}),$o.behavior={},$o.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.substring(e+1),n=n.substring(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},$o.event=null,$o.requote=function(n){return n.replace(fa,"\\$&")};var fa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ha={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ga=function(n,t){return t.querySelector(n)},pa=function(n,t){return t.querySelectorAll(n)},va=Go[a(Go,"matchesSelector")],da=function(n,t){return va.call(n,t)};"function"==typeof Sizzle&&(ga=function(n,t){return Sizzle(n,t)[0]||null},pa=function(n,t){return Sizzle.uniqueSort(Sizzle(n,t))},da=Sizzle.matchesSelector),$o.selection=function(){return Ma};var ma=$o.selection.prototype=[];ma.select=function(n){var t,e,r,u,i=[];n=v(n);for(var o=-1,a=this.length;++o=0&&(e=n.substring(0,t),n=n.substring(t+1)),ya.hasOwnProperty(e)?{space:ya[e],local:n}:n}},ma.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=$o.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(m(t,n[t]));return this}return this.each(m(n,t))},ma.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=n.trim().split(/^|\s+/g)).length,u=-1;if(t=e.classList){for(;++ur){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(b(e,n[e],t));return this}if(2>r)return Ko.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(b(n,t,e))},ma.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(w(t,n[t]));return this}return this.each(w(n,t))},ma.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},ma.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},ma.append=function(n){return n=S(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},ma.insert=function(n,t){return n=S(n),t=v(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},ma.remove=function(){return this.each(function(){var n=this.parentNode;n&&n.removeChild(this)})},ma.data=function(n,t){function e(n,e){var r,i,o,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new u,y=new u,x=[];for(r=-1;++rr;++r)p[r]=k(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,c.push(p),s.push(g),l.push(v)}var r,i,o=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++oi;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return p(u)},ma.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},ma.sort=function(n){n=A.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},ma.size=function(){var n=0;return this.each(function(){++n}),n};var xa=[];$o.selection.enter=N,$o.selection.enter.prototype=xa,xa.append=ma.append,xa.empty=ma.empty,xa.node=ma.node,xa.call=ma.call,xa.size=ma.size,xa.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(q(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(q(n,t,e))};var _a=$o.map({mouseenter:"mouseover",mouseleave:"mouseout"});_a.forEach(function(n){"on"+n in Jo&&_a.remove(n)});var ba="onselectstart"in Jo?null:a(Go.style,"userSelect"),wa=0;$o.mouse=function(n){return P(n,h())};var Sa=/WebKit/.test(Ko.navigator.userAgent)?-1:0;$o.touches=function(n,t){return arguments.length<2&&(t=h().touches),t?Wo(t).map(function(t){var e=P(n,t);return e.identifier=t.identifier,e}):[]},$o.behavior.drag=function(){function n(){this.on("mousedown.drag",o).on("touchstart.drag",a)}function t(){return $o.event.changedTouches[0].identifier}function e(n,t){return $o.touches(n).filter(function(n){return n.identifier===t})[0]}function r(n,t,e,r){return function(){function o(){var n=t(l,g),e=n[0]-v[0],r=n[1]-v[1];d|=e|r,v=n,f({type:"drag",x:n[0]+c[0],y:n[1]+c[1],dx:e,dy:r})}function a(){m.on(e+"."+p,null).on(r+"."+p,null),y(d&&$o.event.target===h),f({type:"dragend"})}var c,s=this,l=s.parentNode,f=u.of(s,arguments),h=$o.event.target,g=n(),p=null==g?"drag":"drag-"+g,v=t(l,g),d=0,m=$o.select(Ko).on(e+"."+p,o).on(r+"."+p,a),y=D();i?(c=i.apply(s,arguments),c=[c.x-v[0],c.y-v[1]]):c=[0,0],f({type:"dragstart"})}}var u=g(n,"drag","dragstart","dragend"),i=null,o=r(c,$o.mouse,"mousemove","mouseup"),a=r(t,e,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},$o.rebind(n,u,"on")};var ka=Math.PI,Ea=2*ka,Aa=ka/2,Ca=1e-6,Na=Ca*Ca,La=ka/180,Ta=180/ka,qa=Math.SQRT2,za=2,Ra=4;$o.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=O(v),o=i/(za*h)*(e*Y(qa*t+v)-F(v));return[r+o*s,u+o*l,i*e/O(qa*t+v)]}return[r+n*s,u+n*l,i*Math.exp(qa*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],s=o-r,l=a-u,f=s*s+l*l,h=Math.sqrt(f),g=(c*c-i*i+Ra*f)/(2*i*za*h),p=(c*c-i*i-Ra*f)/(2*c*za*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/qa;return e.duration=1e3*y,e},$o.behavior.zoom=function(){function n(n){n.on(A,s).on(Ua+".zoom",h).on(C,p).on("dblclick.zoom",v).on(L,l)}function t(n){return[(n[0]-S.x)/S.k,(n[1]-S.y)/S.k]}function e(n){return[n[0]*S.k+S.x,n[1]*S.k+S.y]}function r(n){S.k=Math.max(E[0],Math.min(E[1],n))}function u(n,t){t=e(t),S.x+=n[0]-t[0],S.y+=n[1]-t[1]}function i(){_&&_.domain(M.range().map(function(n){return(n-S.x)/S.k}).map(M.invert)),w&&w.domain(b.range().map(function(n){return(n-S.y)/S.k}).map(b.invert))}function o(n){n({type:"zoomstart"})}function a(n){i(),n({type:"zoom",scale:S.k,translate:[S.x,S.y]})}function c(n){n({type:"zoomend"})}function s(){function n(){l=1,u($o.mouse(r),h),a(i)}function e(){f.on(C,Ko===r?p:null).on(N,null),g(l&&$o.event.target===s),c(i)}var r=this,i=q.of(r,arguments),s=$o.event.target,l=0,f=$o.select(Ko).on(C,n).on(N,e),h=t($o.mouse(r)),g=D();T.call(r),o(i)}function l(){function n(){var n=$o.touches(p);return g=S.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=t(n))}),n}function e(){for(var t=$o.event.changedTouches,e=0,i=t.length;i>e;++e)d[t[e].identifier]=null;var o=n(),c=Date.now();if(1===o.length){if(500>c-x){var s=o[0],l=d[s.identifier];r(2*S.k),u(s,l),f(),a(v)}x=c}else if(o.length>1){var s=o[0],h=o[1],g=s[0]-h[0],p=s[1]-h[1];m=g*g+p*p}}function i(){for(var n,t,e,i,o=$o.touches(p),c=0,s=o.length;s>c;++c,i=null)if(e=o[c],i=d[e.identifier]){if(t)break;n=e,t=i}if(i){var l=(l=e[0]-n[0])*l+(l=e[1]-n[1])*l,f=m&&Math.sqrt(l/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+i[0])/2,(t[1]+i[1])/2],r(f*g)}x=null,u(n,t),a(v)}function h(){if($o.event.touches.length){for(var t=$o.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}b.on(M,null).on(_,null),w.on(A,s).on(L,l),k(),c(v)}var g,p=this,v=q.of(p,arguments),d={},m=0,y=$o.event.changedTouches[0].identifier,M="touchmove.zoom-"+y,_="touchend.zoom-"+y,b=$o.select(Ko).on(M,i).on(_,h),w=$o.select(p).on(A,null).on(L,e),k=D();T.call(p),e(),o(v)}function h(){var n=q.of(this,arguments);y?clearTimeout(y):(T.call(this),o(n)),y=setTimeout(function(){y=null,c(n)},50),f();var e=m||$o.mouse(this);d||(d=t(e)),r(Math.pow(2,.002*Da())*S.k),u(e,d),a(n)}function p(){d=null}function v(){var n=q.of(this,arguments),e=$o.mouse(this),i=t(e),s=Math.log(S.k)/Math.LN2;o(n),r(Math.pow(2,$o.event.shiftKey?Math.ceil(s)-1:Math.floor(s)+1)),u(e,i),a(n),c(n)}var d,m,y,x,M,_,b,w,S={x:0,y:0,k:1},k=[960,500],E=Pa,A="mousedown.zoom",C="mousemove.zoom",N="mouseup.zoom",L="touchstart.zoom",q=g(n,"zoomstart","zoom","zoomend");return n.event=function(n){n.each(function(){var n=q.of(this,arguments),t=S;Ss?$o.select(this).transition().each("start.zoom",function(){S=this.__chart__||{x:0,y:0,k:1},o(n)}).tween("zoom:zoom",function(){var e=k[0],r=k[1],u=e/2,i=r/2,o=$o.interpolateZoom([(u-S.x)/S.k,(i-S.y)/S.k,e/S.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),c=e/r[2];this.__chart__=S={x:u-r[0]*c,y:i-r[1]*c,k:c},a(n)}}).each("end.zoom",function(){c(n)}):(this.__chart__=S,o(n),a(n),c(n))})},n.translate=function(t){return arguments.length?(S={x:+t[0],y:+t[1],k:S.k},i(),n):[S.x,S.y]},n.scale=function(t){return arguments.length?(S={x:S.x,y:S.y,k:+t},i(),n):S.k},n.scaleExtent=function(t){return arguments.length?(E=null==t?Pa:[+t[0],+t[1]],n):E},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(k=t&&[+t[0],+t[1]],n):k},n.x=function(t){return arguments.length?(_=t,M=t.copy(),S={x:0,y:0,k:1},n):_},n.y=function(t){return arguments.length?(w=t,b=t.copy(),S={x:0,y:0,k:1},n):w},$o.rebind(n,q,"on")};var Da,Pa=[0,1/0],Ua="onwheel"in Jo?(Da=function(){return-$o.event.deltaY*($o.event.deltaMode?120:1)},"wheel"):"onmousewheel"in Jo?(Da=function(){return $o.event.wheelDelta},"mousewheel"):(Da=function(){return-$o.event.detail},"MozMousePixelScroll");Z.prototype.toString=function(){return this.rgb()+""},$o.hsl=function(n,t,e){return 1===arguments.length?n instanceof X?V(n.h,n.s,n.l):st(""+n,lt,V):V(+n,+t,+e)};var ja=X.prototype=new Z;ja.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),V(this.h,this.s,this.l/n)},ja.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),V(this.h,this.s,n*this.l)},ja.rgb=function(){return $(this.h,this.s,this.l)},$o.hcl=function(n,t,e){return 1===arguments.length?n instanceof W?B(n.h,n.c,n.l):n instanceof K?nt(n.l,n.a,n.b):nt((n=ft((n=$o.rgb(n)).r,n.g,n.b)).l,n.a,n.b):B(+n,+t,+e)};var Ha=W.prototype=new Z;Ha.brighter=function(n){return B(this.h,this.c,Math.min(100,this.l+Fa*(arguments.length?n:1)))},Ha.darker=function(n){return B(this.h,this.c,Math.max(0,this.l-Fa*(arguments.length?n:1)))},Ha.rgb=function(){return J(this.h,this.c,this.l).rgb()},$o.lab=function(n,t,e){return 1===arguments.length?n instanceof K?G(n.l,n.a,n.b):n instanceof W?J(n.l,n.c,n.h):ft((n=$o.rgb(n)).r,n.g,n.b):G(+n,+t,+e)};var Fa=18,Oa=.95047,Ya=1,Ia=1.08883,Za=K.prototype=new Z;Za.brighter=function(n){return G(Math.min(100,this.l+Fa*(arguments.length?n:1)),this.a,this.b)},Za.darker=function(n){return G(Math.max(0,this.l-Fa*(arguments.length?n:1)),this.a,this.b)},Za.rgb=function(){return Q(this.l,this.a,this.b)},$o.rgb=function(n,t,e){return 1===arguments.length?n instanceof at?ot(n.r,n.g,n.b):st(""+n,ot,$):ot(~~n,~~t,~~e)};var Va=at.prototype=new Z;Va.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),ot(Math.min(255,~~(t/n)),Math.min(255,~~(e/n)),Math.min(255,~~(r/n)))):ot(u,u,u)},Va.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),ot(~~(n*this.r),~~(n*this.g),~~(n*this.b))},Va.hsl=function(){return lt(this.r,this.g,this.b)},Va.toString=function(){return"#"+ct(this.r)+ct(this.g)+ct(this.b)};var Xa=$o.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});Xa.forEach(function(n,t){Xa.set(n,ut(t))}),$o.functor=pt,$o.xhr=dt(vt),$o.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=$o.xhr(n,t,i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o.row(e)}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function o(t){return t.map(a).join(n)}function a(n){return c.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var c=new RegExp('["'+n+"\n]"),s=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(l>=c)return o;if(u)return u=!1,i;var t=l;if(34===n.charCodeAt(t)){for(var e=t;e++l;){var r=n.charCodeAt(l++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(l)&&(++l,++a);else if(r!==s)continue;return n.substring(t,l-a)}return n.substring(t)}for(var r,u,i={},o={},a=[],c=n.length,l=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();(!t||(h=t(h,f++)))&&a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new i,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(a).join(n)].concat(t.map(function(t){return u.map(function(n){return a(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(o).join("\n")},e},$o.csv=$o.dsv(",","text/csv"),$o.tsv=$o.dsv(" ","text/tab-separated-values");var $a,Ba,Wa,Ja,Ga,Ka=Ko[a(Ko,"requestAnimationFrame")]||function(n){setTimeout(n,17)};$o.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};Ba?Ba.n=i:$a=i,Ba=i,Wa||(Ja=clearTimeout(Ja),Wa=1,Ka(xt))},$o.timer.flush=function(){Mt(),_t()};var Qa=".",nc=",",tc=[3,3],ec="$",rc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(bt);$o.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=$o.round(n,wt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((0>=e?e+1:e-1)/3)))),rc[8+e/3]},$o.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)},$o.format=function(n){var t=uc.exec(n),e=t[1]||" ",r=t[2]||">",u=t[3]||"",i=t[4]||"",o=t[5],a=+t[6],c=t[7],s=t[8],l=t[9],f=1,h="",g=!1;switch(s&&(s=+s.substring(1)),(o||"0"===e&&"="===r)&&(o=e="0",r="=",c&&(a-=Math.floor((a-1)/4))),l){case"n":c=!0,l="g";break;case"%":f=100,h="%",l="f";break;case"p":f=100,h="%",l="r";break;case"b":case"o":case"x":case"X":"#"===i&&(i="0"+l.toLowerCase());case"c":case"d":g=!0,s=0;break;case"s":f=-1,l="r"}"#"===i?i="":"$"===i&&(i=ec),"r"!=l||s||(l="g"),null!=s&&("g"==l?s=Math.max(1,Math.min(21,s)):("e"==l||"f"==l)&&(s=Math.max(0,Math.min(20,s)))),l=ic.get(l)||St;var p=o&&c;return function(n){if(g&&n%1)return"";var t=0>n||0===n&&0>1/n?(n=-n,"-"):u;if(0>f){var v=$o.formatPrefix(n,s);n=v.scale(n),h=v.symbol}else n*=f;n=l(n,s);var d=n.lastIndexOf("."),m=0>d?n:n.substring(0,d),y=0>d?"":Qa+n.substring(d+1);!o&&c&&(m=oc(m));var x=i.length+m.length+y.length+(p?0:t.length),M=a>x?new Array(x=a-x+1).join(e):"";return p&&(m=oc(M+m)),t+=i,n=m+y,("<"===r?t+n+M:">"===r?M+t+n:"^"===r?M.substring(0,x>>=1)+t+n+M.substring(x):t+(p?n:M+n))+h}};var uc=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,ic=$o.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=$o.round(n,wt(n,t))).toFixed(Math.max(0,Math.min(20,wt(n*(1+1e-15),t))))}}),oc=vt;if(tc){var ac=tc.length;oc=function(n){for(var t=n.length,e=[],r=0,u=tc[0];t>0&&u>0;)e.push(n.substring(t-=u,t+u)),u=tc[r=(r+1)%ac];return e.reverse().join(nc)}}$o.geo={},kt.prototype={s:0,t:0,add:function(n){Et(n,this.t,cc),Et(cc.s,this.s,this),this.s?this.t+=cc.t:this.s=cc.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var cc=new kt;$o.geo.stream=function(n,t){n&&sc.hasOwnProperty(n.type)?sc[n.type](n,t):At(n,t)};var sc={Feature:function(n,t){At(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*ka+n:n,gc.lineStart=gc.lineEnd=gc.point=c}};$o.geo.bounds=function(){function n(n,t){x.push(M=[l=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=Tt([t*La,e*La]);if(m){var u=zt(m,r),i=[u[1],-u[0],0],o=zt(i,u);Pt(o),o=Ut(o);var c=t-p,s=c>0?1:-1,v=o[0]*Ta*s,d=aa(c)>180;if(d^(v>s*p&&s*t>v)){var y=o[1]*Ta;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>s*p&&s*t>v)){var y=-o[1]*Ta;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t):h>=l?(l>t&&(l=t),t>h&&(h=t)):t>p?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t)}else n(t,e);m=r,p=t}function e(){_.point=t}function r(){M[0]=l,M[1]=h,_.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=aa(r)>180?r+(r>0?360:-360):r}else v=n,d=e;gc.point(n,e),t(n,e)}function i(){gc.lineStart()}function o(){u(v,d),gc.lineEnd(),aa(y)>Ca&&(l=-(h=180)),M[0]=l,M[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function s(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nhc?(l=-(h=180),f=-(g=90)):y>Ca?g=90:-Ca>y&&(f=-90),M[0]=l,M[1]=h}};return function(n){g=h=-(l=f=1/0),x=[],$o.geo.stream(n,_);var t=x.length;if(t){x.sort(c);for(var e,r=1,u=x[0],i=[u];t>r;++r)e=x[r],s(e[0],u)||s(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,l=e[0],h=u[1])}return x=M=null,1/0===l||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[l,f],[h,g]]}}(),$o.geo.centroid=function(n){pc=vc=dc=mc=yc=xc=Mc=_c=bc=wc=Sc=0,$o.geo.stream(n,kc);var t=bc,e=wc,r=Sc,u=t*t+e*e+r*r;return Na>u&&(t=xc,e=Mc,r=_c,Ca>vc&&(t=dc,e=mc,r=yc),u=t*t+e*e+r*r,Na>u)?[0/0,0/0]:[Math.atan2(e,t)*Ta,H(r/Math.sqrt(u))*Ta]};var pc,vc,dc,mc,yc,xc,Mc,_c,bc,wc,Sc,kc={sphere:c,point:Ht,lineStart:Ot,lineEnd:Yt,polygonStart:function(){kc.lineStart=It},polygonEnd:function(){kc.lineStart=Ot}},Ec=Bt(Zt,Qt,te,[-ka,-ka/2]),Ac=1e9;$o.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=ue(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},($o.geo.conicEqualArea=function(){return oe(ae)}).raw=ae,$o.geo.albers=function(){return $o.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},$o.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=$o.geo.albers(),o=$o.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=$o.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var s=i.scale(),l=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[l-.455*s,f-.238*s],[l+.455*s,f+.238*s]]).stream(c).point,r=o.translate([l-.307*s,f+.201*s]).clipExtent([[l-.425*s+Ca,f+.12*s+Ca],[l-.214*s-Ca,f+.234*s-Ca]]).stream(c).point,u=a.translate([l-.205*s,f+.212*s]).clipExtent([[l-.214*s+Ca,f+.166*s+Ca],[l-.115*s-Ca,f+.234*s-Ca]]).stream(c).point,n},n.scale(1070)};var Cc,Nc,Lc,Tc,qc,zc,Rc={point:c,lineStart:c,lineEnd:c,polygonStart:function(){Nc=0,Rc.lineStart=ce},polygonEnd:function(){Rc.lineStart=Rc.lineEnd=Rc.point=c,Cc+=aa(Nc/2)}},Dc={point:se,lineStart:c,lineEnd:c,polygonStart:c,polygonEnd:c},Pc={point:he,lineStart:ge,lineEnd:pe,polygonStart:function(){Pc.lineStart=ve},polygonEnd:function(){Pc.point=he,Pc.lineStart=ge,Pc.lineEnd=pe}};$o.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),$o.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return Cc=0,$o.geo.stream(n,u(Rc)),Cc},n.centroid=function(n){return dc=mc=yc=xc=Mc=_c=bc=wc=Sc=0,$o.geo.stream(n,u(Pc)),Sc?[bc/Sc,wc/Sc]:_c?[xc/_c,Mc/_c]:yc?[dc/yc,mc/yc]:[0/0,0/0]},n.bounds=function(n){return qc=zc=-(Lc=Tc=1/0),$o.geo.stream(n,u(Dc)),[[Lc,Tc],[qc,zc]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||ye(n):vt,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new le:new de(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection($o.geo.albersUsa()).context(null)},$o.geo.transform=function(n){return{stream:function(t){var e=new xe(t);for(var r in n)e[r]=n[r];return e}}},xe.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart() -},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},$o.geo.projection=_e,$o.geo.projectionMutator=be,($o.geo.equirectangular=function(){return _e(Se)}).raw=Se.invert=Se,$o.geo.rotation=function(n){function t(t){return t=n(t[0]*La,t[1]*La),t[0]*=Ta,t[1]*=Ta,t}return n=Ee(n[0]%360*La,n[1]*La,n.length>2?n[2]*La:0),t.invert=function(t){return t=n.invert(t[0]*La,t[1]*La),t[0]*=Ta,t[1]*=Ta,t},t},ke.invert=Se,$o.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=Ee(-n[0]*La,-n[1]*La,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ta,n[1]*=Ta}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=Le((t=+r)*La,u*La),n):t},n.precision=function(r){return arguments.length?(e=Le(t*La,(u=+r)*La),n):u},n.angle(90)},$o.geo.distance=function(n,t){var e,r=(t[0]-n[0])*La,u=n[1]*La,i=t[1]*La,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),s=Math.cos(u),l=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=s*l-c*f*a)*e),c*l+s*f*a)},$o.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return $o.range(Math.ceil(i/d)*d,u,d).map(h).concat($o.range(Math.ceil(s/m)*m,c,m).map(g)).concat($o.range(Math.ceil(r/p)*p,e,p).filter(function(n){return aa(n%d)>Ca}).map(l)).concat($o.range(Math.ceil(a/v)*v,o,v).filter(function(n){return aa(n%m)>Ca}).map(f))}var e,r,u,i,o,a,c,s,l,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(s).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],s=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),s>c&&(t=s,s=c,c=t),n.precision(y)):[[i,s],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,l=qe(a,o,90),f=ze(r,e,y),h=qe(s,c,90),g=ze(i,u,y),n):y},n.majorExtent([[-180,-90+Ca],[180,90-Ca]]).minorExtent([[-180,-80-Ca],[180,80+Ca]])},$o.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=Re,u=De;return n.distance=function(){return $o.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},$o.geo.interpolate=function(n,t){return Pe(n[0]*La,n[1]*La,t[0]*La,t[1]*La)},$o.geo.length=function(n){return Uc=0,$o.geo.stream(n,jc),Uc};var Uc,jc={sphere:c,point:c,lineStart:Ue,lineEnd:c,polygonStart:c,polygonEnd:c},Hc=je(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});($o.geo.azimuthalEqualArea=function(){return _e(Hc)}).raw=Hc;var Fc=je(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},vt);($o.geo.azimuthalEquidistant=function(){return _e(Fc)}).raw=Fc,($o.geo.conicConformal=function(){return oe(He)}).raw=He,($o.geo.conicEquidistant=function(){return oe(Fe)}).raw=Fe;var Oc=je(function(n){return 1/n},Math.atan);($o.geo.gnomonic=function(){return _e(Oc)}).raw=Oc,Oe.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Aa]},($o.geo.mercator=function(){return Ye(Oe)}).raw=Oe;var Yc=je(function(){return 1},Math.asin);($o.geo.orthographic=function(){return _e(Yc)}).raw=Yc;var Ic=je(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});($o.geo.stereographic=function(){return _e(Ic)}).raw=Ic,Ie.invert=function(n,t){return[Math.atan2(F(n),Math.cos(t)),H(Math.sin(t)/O(n))]},($o.geo.transverseMercator=function(){return Ye(Ie)}).raw=Ie,$o.geom={},$o.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u,i,o,a,c,s,l,f,h,g,p,v=pt(e),d=pt(r),m=n.length,y=m-1,x=[],M=[],_=0;if(v===Ze&&r===Ve)t=n;else for(i=0,t=[];m>i;++i)t.push([+v.call(this,u=n[i],i),+d.call(this,u,i)]);for(i=1;m>i;++i)(t[i][1]i;++i)i!==_&&(c=t[i][1]-t[_][1],a=t[i][0]-t[_][0],x.push({angle:Math.atan2(c,a),index:i}));for(x.sort(function(n,t){return n.angle-t.angle}),g=x[0].angle,h=x[0].index,f=0,i=1;y>i;++i){if(o=x[i].index,g==x[i].angle){if(a=t[h][0]-t[_][0],c=t[h][1]-t[_][1],s=t[o][0]-t[_][0],l=t[o][1]-t[_][1],a*a+c*c>=s*s+l*l){x[i].index=-1;continue}x[f].index=-1}g=x[i].angle,f=i,h=o}for(M.push(_),i=0,o=0;2>i;++o)x[o].index>-1&&(M.push(x[o].index),i++);for(p=M.length;y>o;++o)if(!(x[o].index<0)){for(;!Xe(M[p-2],M[p-1],x[o].index,t);)--p;M[p++]=x[o].index}var b=[];for(i=p-1;i>=0;--i)b.push(n[M[i]]);return b}var e=Ze,r=Ve;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t)},$o.geom.polygon=function(n){return ha(n,Zc),n};var Zc=$o.geom.polygon.prototype=[];Zc.area=function(){for(var n,t=-1,e=this.length,r=this[e-1],u=0;++t=r&&s.x<=i&&s.y>=u&&s.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];l.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Ca)*Ca,y:Math.round(o(n,t)/Ca)*Ca,i:t}})}var r=Ze,u=Ve,i=r,o=u,a=Kc;return n?t(n):(t.links=function(n){return _r(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return _r(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(ir),c=-1,s=a.length,l=a[s-1].edge,f=l.l===o?l.r:l.l;++c=s,h=r>=l,g=(h<<1)+f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=Er()),f?u=s:a=s,h?o=l:c=l,i(n,t,e,r,u,o,a,c)}var l,f,h,g,p,v,d,m,y,x=pt(a),M=pt(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)l=n[g],l.xm&&(m=l.x),l.y>y&&(y=l.y),f.push(l.x),h.push(l.y);else for(g=0;p>g;++g){var _=+x(l=n[g],g),b=+M(l,g);v>_&&(v=_),d>b&&(d=b),_>m&&(m=_),b>y&&(y=b),f.push(_),h.push(b)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=Er();if(k.add=function(n){i(k,n,+x(n,++g),+M(n,g),v,d,m,y)},k.visit=function(n){Ar(n,k,v,d,m,y)},g=-1,null==t){for(;++g=0?n.substring(0,t):n,r=t>=0?n.substring(t+1):"in";return e=ts.get(e)||ns,r=es.get(r)||vt,Rr(r(e.apply(null,Bo.call(arguments,1))))},$o.interpolateHcl=$r,$o.interpolateHsl=Br,$o.interpolateLab=Wr,$o.interpolateRound=Jr,$o.transform=function(n){var t=Jo.createElementNS($o.ns.prefix.svg,"g");return($o.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Gr(e?e.matrix:rs)})(n)},Gr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var rs={a:1,b:0,c:0,d:1,e:0,f:0};$o.interpolateTransform=tu,$o.layout={},$o.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e(u-e)*a){var c=t.charge*a*a;return n.px-=i*c,n.py-=o*c,!0}if(t.point&&isFinite(a)){var c=t.pointCharge*a*a;n.px-=i*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=$o.event.x,n.py=$o.event.y,a.resume()}var e,r,u,i,o,a={},c=$o.dispatch("start","tick","end"),s=[1,1],l=.9,f=us,h=is,g=-30,p=.1,v=.8,d=[],m=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,v,y,x,M,_=d.length,b=m.length;for(e=0;b>e;++e)a=m[e],f=a.source,h=a.target,x=h.x-f.x,M=h.y-f.y,(v=x*x+M*M)&&(v=r*i[e]*((v=Math.sqrt(v))-u[e])/v,x*=v,M*=v,h.x-=x*(y=f.weight/(h.weight+f.weight)),h.y-=M*y,f.x+=x*(y=1-y),f.y+=M*y);if((y=r*p)&&(x=s[0]/2,M=s[1]/2,e=-1,y))for(;++e<_;)a=d[e],a.x+=(x-a.x)*y,a.y+=(M-a.y)*y;if(g)for(fu(t=$o.geom.quadtree(d),r,o),e=-1;++e<_;)(a=d[e]).fixed||t.visit(n(a));for(e=-1;++e<_;)a=d[e],a.fixed?(a.x=a.px,a.y=a.py):(a.x-=(a.px-(a.px=a.x))*l,a.y-=(a.py-(a.py=a.y))*l);c.tick({type:"tick",alpha:r})},a.nodes=function(n){return arguments.length?(d=n,a):d},a.links=function(n){return arguments.length?(m=n,a):m},a.size=function(n){return arguments.length?(s=n,a):s},a.linkDistance=function(n){return arguments.length?(f="function"==typeof n?n:+n,a):f},a.distance=a.linkDistance,a.linkStrength=function(n){return arguments.length?(h="function"==typeof n?n:+n,a):h},a.friction=function(n){return arguments.length?(l=+n,a):l},a.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,a):g},a.gravity=function(n){return arguments.length?(p=+n,a):p},a.theta=function(n){return arguments.length?(v=+n,a):v},a.alpha=function(n){return arguments.length?(n=+n,r?r=n>0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),$o.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;s>a;++a){var u=m[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,s=o.length;++at;++t)(r=d[t]).index=t,r.weight=0;for(t=0;l>t;++t)r=m[t],"number"==typeof r.source&&(r.source=d[r.source]),"number"==typeof r.target&&(r.target=d[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=d[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;l>t;++t)u[t]=+f.call(this,m[t],t);else for(t=0;l>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;l>t;++t)i[t]=+h.call(this,m[t],t);else for(t=0;l>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,d[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=$o.behavior.drag().origin(vt).on("dragstart.force",au).on("drag.force",t).on("dragend.force",cu)),arguments.length?(this.on("mouseover.force",su).on("mouseout.force",lu).call(e),void 0):e},$o.rebind(a,c,"on")};var us=20,is=1;$o.layout.hierarchy=function(){function n(t,o,a){var c=u.call(e,t,o);if(t.depth=o,a.push(t),c&&(s=c.length)){for(var s,l,f=-1,h=t.children=new Array(s),g=0,p=o+1;++fg;++g)for(u.call(n,s[0][g],p=v[g],l[0][g][1]),h=1;d>h;++h)u.call(n,s[h][g],p+=l[h-1][g][1],l[h][g][1]);return a}var t=vt,e=Mu,r=_u,u=xu,i=mu,o=yu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:as.get(t)||Mu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:cs.get(t)||_u,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var as=$o.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(bu),i=n.map(wu),o=$o.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,s=[],l=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],s.push(e)):(c+=i[e],l.push(e));return l.reverse().concat(s)},reverse:function(n){return $o.range(n.length).reverse()},"default":Mu}),cs=$o.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,s,l=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=s=0,e=1;h>e;++e){for(t=0,u=0;l>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];l>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,s>c&&(s=c)}for(e=0;h>e;++e)g[e]-=s;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:_u});$o.layout.histogram=function(){function n(n,i){for(var o,a,c=[],s=n.map(e,this),l=r.call(this,s,i),f=u.call(this,l,s,i),i=-1,h=s.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=l[0]&&a<=l[1]&&(o=c[$o.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=Au,u=ku;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=pt(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return Eu(n,t)}:pt(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},$o.layout.tree=function(){function n(n,i){function o(n,t){var r=n.children,u=n._tree;if(r&&(i=r.length)){for(var i,a,s,l=r[0],f=l,h=-1;++h0&&(Uu(ju(a,n,r),n,u),s+=u,l+=u),f+=a._tree.mod,s+=i._tree.mod,h+=c._tree.mod,l+=o._tree.mod;a&&!Lu(o)&&(o._tree.thread=a,o._tree.mod+=f-l),i&&!Nu(c)&&(c._tree.thread=i,c._tree.mod+=s-h,r=n)}return r}var s=t.call(this,n,i),l=s[0];Du(l,function(n,t){n._tree={ancestor:n,prelim:0,mod:0,change:0,shift:0,number:t?t._tree.number+1:0}}),o(l),a(l,-l._tree.prelim);var f=Tu(l,zu),h=Tu(l,qu),g=Tu(l,Ru),p=f.x-e(f,h)/2,v=h.x+e(h,f)/2,d=g.depth||1;return Du(l,u?function(n){n.x*=r[0],n.y=n.depth*r[1],delete n._tree}:function(n){n.x=(n.x-p)/(v-p)*r[0],n.y=n.depth/d*r[1],delete n._tree}),s}var t=$o.layout.hierarchy().sort(null).value(null),e=Cu,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},hu(n,t)},$o.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],s=u[1],l=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Du(a,function(n){n.r=+l(n.value)}),Du(a,Iu),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/s))/2;Du(a,function(n){n.r+=f}),Du(a,Iu),Du(a,function(n){n.r-=f})}return Xu(a,c/2,s/2,t?1:1/Math.max(2*a.r/c,2*a.r/s)),o}var t,e=$o.layout.hierarchy().sort(Hu),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},hu(n,e)},$o.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],s=0;Du(c,function(n){var t=n.children;t&&t.length?(n.x=Wu(t),n.y=Bu(t)):(n.x=o?s+=e(n,o):0,n.y=0,o=n)});var l=Ju(c),f=Gu(c),h=l.x-e(l,f)/2,g=f.x+e(f,l)/2;return Du(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=$o.layout.hierarchy().sort(null).value(null),e=Cu,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},hu(n,t)},$o.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,s=f(e),l=[],h=i.slice(),p=1/0,v="slice"===g?s.dx:"dice"===g?s.dy:"slice-dice"===g?1&e.depth?s.dy:s.dx:Math.min(s.dx,s.dy);for(n(h,s.dx*s.dy/e.value),l.area=0;(c=h.length)>0;)l.push(o=h[c-1]),l.area+=o.area,"squarify"!==g||(a=r(l,v))<=p?(h.pop(),p=a):(l.area-=l.pop().area,u(l,v,s,!1),v=Math.min(s.dx,s.dy),l.length=l.area=0,p=1/0);l.length&&(u(l,v,s,!0),l.length=l.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++oe&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,s=e.y,l=t?c(n.area/t):0;if(t==e.dx){for((r||l>e.dy)&&(l=e.dy);++ie.dx)&&(l=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=$o.random.normal.apply($o,arguments);return function(){return Math.exp(n())}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t/n}}},$o.scale={};var ss={floor:vt,ceil:vt};$o.scale.linear=function(){return oi([0,1],[0,1],qr,!1)};var ls={s:1,g:1,p:1,r:1,e:1};$o.scale.log=function(){return pi($o.scale.linear().domain([0,1]),10,!0,[1,10])};var fs=$o.format(".0e"),hs={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};$o.scale.pow=function(){return vi($o.scale.linear(),1,[0,1])},$o.scale.sqrt=function(){return $o.scale.pow().exponent(.5)},$o.scale.ordinal=function(){return mi([],{t:"range",a:[[]]})},$o.scale.category10=function(){return $o.scale.ordinal().range(gs)},$o.scale.category20=function(){return $o.scale.ordinal().range(ps)},$o.scale.category20b=function(){return $o.scale.ordinal().range(vs)},$o.scale.category20c=function(){return $o.scale.ordinal().range(ds)};var gs=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(it),ps=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(it),vs=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(it),ds=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(it);$o.scale.quantile=function(){return yi([],[])},$o.scale.quantize=function(){return xi(0,1,[0,1])},$o.scale.threshold=function(){return Mi([.5],[0,1])},$o.scale.identity=function(){return _i([0,1])},$o.svg={},$o.svg.arc=function(){function n(){var n=t.apply(this,arguments),i=e.apply(this,arguments),o=r.apply(this,arguments)+ms,a=u.apply(this,arguments)+ms,c=(o>a&&(c=o,o=a,a=c),a-o),s=ka>c?"0":"1",l=Math.cos(o),f=Math.sin(o),h=Math.cos(a),g=Math.sin(a);return c>=ys?n?"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"M0,"+n+"A"+n+","+n+" 0 1,0 0,"+-n+"A"+n+","+n+" 0 1,0 0,"+n+"Z":"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"Z":n?"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L"+n*h+","+n*g+"A"+n+","+n+" 0 "+s+",0 "+n*l+","+n*f+"Z":"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L0,0"+"Z"}var t=bi,e=wi,r=Si,u=ki;return n.innerRadius=function(e){return arguments.length?(t=pt(e),n):t},n.outerRadius=function(t){return arguments.length?(e=pt(t),n):e},n.startAngle=function(t){return arguments.length?(r=pt(t),n):r},n.endAngle=function(t){return arguments.length?(u=pt(t),n):u},n.centroid=function(){var n=(t.apply(this,arguments)+e.apply(this,arguments))/2,i=(r.apply(this,arguments)+u.apply(this,arguments))/2+ms;return[Math.cos(i)*n,Math.sin(i)*n]},n};var ms=-Aa,ys=Ea-Ca;$o.svg.line=function(){return Ei(vt)};var xs=$o.map({linear:Ai,"linear-closed":Ci,step:Ni,"step-before":Li,"step-after":Ti,basis:Ui,"basis-open":ji,"basis-closed":Hi,bundle:Fi,cardinal:Ri,"cardinal-open":qi,"cardinal-closed":zi,monotone:Xi});xs.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Ms=[0,2/3,1/3,0],_s=[0,1/3,2/3,0],bs=[0,1/6,2/3,1/6];$o.svg.line.radial=function(){var n=Ei($i);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},Li.reverse=Ti,Ti.reverse=Li,$o.svg.area=function(){return Bi(vt)},$o.svg.area.radial=function(){var n=Bi($i);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},$o.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),s=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,s)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,s.r,s.p0)+r(s.r,s.p1,s.a1-s.a0)+u(s.r,s.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)+ms,l=s.call(n,u,r)+ms;return{r:i,a0:o,a1:l,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(l),i*Math.sin(l)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ka)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=Re,o=De,a=Wi,c=Si,s=ki;return n.radius=function(t){return arguments.length?(a=pt(t),n):a},n.source=function(t){return arguments.length?(i=pt(t),n):i},n.target=function(t){return arguments.length?(o=pt(t),n):o},n.startAngle=function(t){return arguments.length?(c=pt(t),n):c},n.endAngle=function(t){return arguments.length?(s=pt(t),n):s},n},$o.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=Re,e=De,r=Ji;return n.source=function(e){return arguments.length?(t=pt(e),n):t},n.target=function(t){return arguments.length?(e=pt(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},$o.svg.diagonal.radial=function(){var n=$o.svg.diagonal(),t=Ji,e=n.projection;return n.projection=function(n){return arguments.length?e(Gi(t=n)):t},n},$o.svg.symbol=function(){function n(n,r){return(ws.get(t.call(this,n,r))||no)(e.call(this,n,r))}var t=Qi,e=Ki;return n.type=function(e){return arguments.length?(t=pt(e),n):t},n.size=function(t){return arguments.length?(e=pt(t),n):e},n};var ws=$o.map({circle:no,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*As)),e=t*As;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Es),e=t*Es/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Es),e=t*Es/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});$o.svg.symbolTypes=ws.keys();var Ss,ks,Es=Math.sqrt(3),As=Math.tan(30*La),Cs=[],Ns=0; -Cs.call=ma.call,Cs.empty=ma.empty,Cs.node=ma.node,Cs.size=ma.size,$o.transition=function(n){return arguments.length?Ss?n.transition():n:Ma.transition()},$o.transition.prototype=Cs,Cs.select=function(n){var t,e,r,u=this.id,i=[];n=v(n);for(var o=-1,a=this.length;++oi;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return to(u,this.id)},Cs.tween=function(n,t){var e=this.id;return arguments.length<2?this.node().__transition__[e].tween.get(n):C(this,null==t?function(t){t.__transition__[e].tween.remove(n)}:function(r){r.__transition__[e].tween.set(n,t)})},Cs.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?tu:qr,a=$o.ns.qualify(n);return eo(this,"attr."+n,t,a.local?i:u)},Cs.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=$o.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Cs.style=function(n,t,e){function r(){this.style.removeProperty(n)}function u(t){return null==t?r:(t+="",function(){var r,u=Ko.getComputedStyle(this,null).getPropertyValue(n);return u!==t&&(r=qr(u,t),function(t){this.style.setProperty(n,r(t),e)})})}var i=arguments.length;if(3>i){if("string"!=typeof n){2>i&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return eo(this,"style."+n,t,u)},Cs.styleTween=function(n,t,e){function r(r,u){var i=t.call(this,r,u,Ko.getComputedStyle(this,null).getPropertyValue(n));return i&&function(t){this.style.setProperty(n,i(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Cs.text=function(n){return eo(this,"text",n,ro)},Cs.remove=function(){return this.each("end.transition",function(){var n;this.__transition__.count<2&&(n=this.parentNode)&&n.removeChild(this)})},Cs.ease=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].ease:("function"!=typeof n&&(n=$o.ease.apply($o,arguments)),C(this,function(e){e.__transition__[t].ease=n}))},Cs.delay=function(n){var t=this.id;return C(this,"function"==typeof n?function(e,r,u){e.__transition__[t].delay=+n.call(e,e.__data__,r,u)}:(n=+n,function(e){e.__transition__[t].delay=n}))},Cs.duration=function(n){var t=this.id;return C(this,"function"==typeof n?function(e,r,u){e.__transition__[t].duration=Math.max(1,n.call(e,e.__data__,r,u))}:(n=Math.max(1,n),function(e){e.__transition__[t].duration=n}))},Cs.each=function(n,t){var e=this.id;if(arguments.length<2){var r=ks,u=Ss;Ss=e,C(this,function(t,r,u){ks=t.__transition__[e],n.call(t,t.__data__,r,u)}),ks=r,Ss=u}else C(this,function(r){var u=r.__transition__[e];(u.event||(u.event=$o.dispatch("start","end"))).on(n,t)});return this},Cs.transition=function(){for(var n,t,e,r,u=this.id,i=++Ns,o=[],a=0,c=this.length;c>a;a++){o.push(n=[]);for(var t=this[a],s=0,l=t.length;l>s;s++)(e=t[s])&&(r=Object.create(e.__transition__[u]),r.delay+=r.duration,uo(e,s,i,r)),n.push(e)}return to(o,i)},$o.svg.axis=function(){function n(n){n.each(function(){var n,s=$o.select(this),l=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):vt:t,p=s.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Ca),d=$o.transition(p.exit()).style("opacity",Ca).remove(),m=$o.transition(p).style("opacity",1),y=ti(f),x=s.selectAll(".domain").data([0]),M=(x.enter().append("path").attr("class","domain"),$o.transition(x));v.append("line"),v.append("text");var _=v.select("line"),b=m.select("line"),w=p.select("text").text(g),S=v.select("text"),k=m.select("text");switch(r){case"bottom":n=io,_.attr("y2",u),S.attr("y",Math.max(u,0)+o),b.attr("x2",0).attr("y2",u),k.attr("x",0).attr("y",Math.max(u,0)+o),w.attr("dy",".71em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+i+"V0H"+y[1]+"V"+i);break;case"top":n=io,_.attr("y2",-u),S.attr("y",-(Math.max(u,0)+o)),b.attr("x2",0).attr("y2",-u),k.attr("x",0).attr("y",-(Math.max(u,0)+o)),w.attr("dy","0em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+-i+"V0H"+y[1]+"V"+-i);break;case"left":n=oo,_.attr("x2",-u),S.attr("x",-(Math.max(u,0)+o)),b.attr("x2",-u).attr("y2",0),k.attr("x",-(Math.max(u,0)+o)).attr("y",0),w.attr("dy",".32em").style("text-anchor","end"),M.attr("d","M"+-i+","+y[0]+"H0V"+y[1]+"H"+-i);break;case"right":n=oo,_.attr("x2",u),S.attr("x",Math.max(u,0)+o),b.attr("x2",u).attr("y2",0),k.attr("x",Math.max(u,0)+o).attr("y",0),w.attr("dy",".32em").style("text-anchor","start"),M.attr("d","M"+i+","+y[0]+"H0V"+y[1]+"H"+i)}if(f.rangeBand){var E=f,A=E.rangeBand()/2;l=f=function(n){return E(n)+A}}else l.rangeBand?l=f:d.call(n,f);v.call(n,l),m.call(n,f)})}var t,e=$o.scale.linear(),r=Ls,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Ts?t+"":Ls,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Ls="bottom",Ts={top:1,right:1,bottom:1,left:1};$o.svg.brush=function(){function n(i){i.each(function(){var i=$o.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=i.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),i.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=i.selectAll(".resize").data(d,vt);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return qs[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,f=$o.transition(i),h=$o.transition(o);c&&(l=ti(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),e(f)),s&&(l=ti(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),r(f)),t(f)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+l[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function e(n){n.select(".extent").attr("x",l[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",l[1]-l[0])}function r(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==$o.event.keyCode&&(C||(x=null,L[0]-=l[1],L[1]-=h[1],C=2),f())}function g(){32==$o.event.keyCode&&2==C&&(L[0]+=l[1],L[1]+=h[1],C=0,f())}function d(){var n=$o.mouse(_),u=!1;M&&(n[0]+=M[0],n[1]+=M[1]),C||($o.event.altKey?(x||(x=[(l[0]+l[1])/2,(h[0]+h[1])/2]),L[0]=l[+(n[0]f?(u=r,r=f):u=f),g[0]!=r||g[1]!=u?(e?o=null:i=null,g[0]=r,g[1]=u,!0):void 0}function y(){d(),S.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),$o.select("body").style("cursor",null),T.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),N(),w({type:"brushend"})}var x,M,_=this,b=$o.select($o.event.target),w=a.of(_,arguments),S=$o.select(_),k=b.datum(),E=!/^(n|s)$/.test(k)&&c,A=!/^(e|w)$/.test(k)&&s,C=b.classed("extent"),N=D(),L=$o.mouse(_),T=$o.select(Ko).on("keydown.brush",u).on("keyup.brush",g);if($o.event.changedTouches?T.on("touchmove.brush",d).on("touchend.brush",y):T.on("mousemove.brush",d).on("mouseup.brush",y),S.interrupt().selectAll("*").interrupt(),C)L[0]=l[0]-L[0],L[1]=h[0]-L[1];else if(k){var q=+/w$/.test(k),z=+/^n/.test(k);M=[l[1-q]-L[0],h[1-z]-L[1]],L[0]=l[q],L[1]=h[z]}else $o.event.altKey&&(x=L.slice());S.style("pointer-events","none").selectAll(".resize").style("display",null),$o.select("body").style("cursor",b.style("cursor")),w({type:"brushstart"}),d()}var i,o,a=g(n,"brushstart","brush","brushend"),c=null,s=null,l=[0,0],h=[0,0],p=!0,v=!0,d=zs[0];return n.event=function(n){n.each(function(){var n=a.of(this,arguments),t={x:l,y:h,i:i,j:o},e=this.__chart__||t;this.__chart__=t,Ss?$o.select(this).transition().each("start.brush",function(){i=e.i,o=e.j,l=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=zr(l,t.x),r=zr(h,t.y);return i=o=null,function(u){l=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){i=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,d=zs[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,d=zs[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(p=!!t[0],v=!!t[1]):c?p=!!t:s&&(v=!!t),n):c&&s?[p,v]:c?p:s?v:null},n.extent=function(t){var e,r,u,a,f;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),i=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(f=e,e=r,r=f),(e!=l[0]||r!=l[1])&&(l=[e,r])),s&&(u=t[0],a=t[1],c&&(u=u[1],a=a[1]),o=[u,a],s.invert&&(u=s(u),a=s(a)),u>a&&(f=u,u=a,a=f),(u!=h[0]||a!=h[1])&&(h=[u,a])),n):(c&&(i?(e=i[0],r=i[1]):(e=l[0],r=l[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(f=e,e=r,r=f))),s&&(o?(u=o[0],a=o[1]):(u=h[0],a=h[1],s.invert&&(u=s.invert(u),a=s.invert(a)),u>a&&(f=u,u=a,a=f))),c&&s?[[e,u],[r,a]]:c?[e,r]:s&&[u,a])},n.clear=function(){return n.empty()||(l=[0,0],h=[0,0],i=o=null),n},n.empty=function(){return!!c&&l[0]==l[1]||!!s&&h[0]==h[1]},$o.rebind(n,a,"on")};var qs={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},zs=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Rs=$o.time={},Ds=Date,Ps=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];ao.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){Us.setUTCDate.apply(this._,arguments)},setDay:function(){Us.setUTCDay.apply(this._,arguments)},setFullYear:function(){Us.setUTCFullYear.apply(this._,arguments)},setHours:function(){Us.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){Us.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){Us.setUTCMinutes.apply(this._,arguments)},setMonth:function(){Us.setUTCMonth.apply(this._,arguments)},setSeconds:function(){Us.setUTCSeconds.apply(this._,arguments)},setTime:function(){Us.setTime.apply(this._,arguments)}};var Us=Date.prototype,js="%a %b %e %X %Y",Hs="%m/%d/%Y",Fs="%H:%M:%S",Os=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],Ys=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],Is=["January","February","March","April","May","June","July","August","September","October","November","December"],Zs=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];Rs.year=co(function(n){return n=Rs.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),Rs.years=Rs.year.range,Rs.years.utc=Rs.year.utc.range,Rs.day=co(function(n){var t=new Ds(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),Rs.days=Rs.day.range,Rs.days.utc=Rs.day.utc.range,Rs.dayOfYear=function(n){var t=Rs.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},Ps.forEach(function(n,t){n=n.toLowerCase(),t=7-t;var e=Rs[n]=co(function(n){return(n=Rs.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=Rs.year(n).getDay();return Math.floor((Rs.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});Rs[n+"s"]=e.range,Rs[n+"s"].utc=e.utc.range,Rs[n+"OfYear"]=function(n){var e=Rs.year(n).getDay();return Math.floor((Rs.dayOfYear(n)+(e+t)%7)/7)}}),Rs.week=Rs.sunday,Rs.weeks=Rs.sunday.range,Rs.weeks.utc=Rs.sunday.utc.range,Rs.weekOfYear=Rs.sundayOfYear,Rs.format=lo;var Vs=ho(Os),Xs=go(Os),$s=ho(Ys),Bs=go(Ys),Ws=ho(Is),Js=go(Is),Gs=ho(Zs),Ks=go(Zs),Qs=/^%/,nl={"-":"",_:" ",0:"0"},tl={a:function(n){return Ys[n.getDay()]},A:function(n){return Os[n.getDay()]},b:function(n){return Zs[n.getMonth()]},B:function(n){return Is[n.getMonth()]},c:lo(js),d:function(n,t){return po(n.getDate(),t,2)},e:function(n,t){return po(n.getDate(),t,2)},H:function(n,t){return po(n.getHours(),t,2)},I:function(n,t){return po(n.getHours()%12||12,t,2)},j:function(n,t){return po(1+Rs.dayOfYear(n),t,3)},L:function(n,t){return po(n.getMilliseconds(),t,3)},m:function(n,t){return po(n.getMonth()+1,t,2)},M:function(n,t){return po(n.getMinutes(),t,2)},p:function(n){return n.getHours()>=12?"PM":"AM"},S:function(n,t){return po(n.getSeconds(),t,2)},U:function(n,t){return po(Rs.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return po(Rs.mondayOfYear(n),t,2)},x:lo(Hs),X:lo(Fs),y:function(n,t){return po(n.getFullYear()%100,t,2)},Y:function(n,t){return po(n.getFullYear()%1e4,t,4)},Z:jo,"%":function(){return"%"}},el={a:vo,A:mo,b:_o,B:bo,c:wo,d:To,e:To,H:zo,I:zo,j:qo,L:Po,m:Lo,M:Ro,p:Uo,S:Do,U:xo,w:yo,W:Mo,x:So,X:ko,y:Ao,Y:Eo,Z:Co,"%":Ho},rl=/^\s*\d+/,ul=$o.map({am:0,pm:1});lo.utc=Fo;var il=Fo("%Y-%m-%dT%H:%M:%S.%LZ");lo.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Oo:il,Oo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Oo.toString=il.toString,Rs.second=co(function(n){return new Ds(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),Rs.seconds=Rs.second.range,Rs.seconds.utc=Rs.second.utc.range,Rs.minute=co(function(n){return new Ds(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),Rs.minutes=Rs.minute.range,Rs.minutes.utc=Rs.minute.utc.range,Rs.hour=co(function(n){var t=n.getTimezoneOffset()/60;return new Ds(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),Rs.hours=Rs.hour.range,Rs.hours.utc=Rs.hour.utc.range,Rs.month=co(function(n){return n=Rs.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),Rs.months=Rs.month.range,Rs.months.utc=Rs.month.utc.range;var ol=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],al=[[Rs.second,1],[Rs.second,5],[Rs.second,15],[Rs.second,30],[Rs.minute,1],[Rs.minute,5],[Rs.minute,15],[Rs.minute,30],[Rs.hour,1],[Rs.hour,3],[Rs.hour,6],[Rs.hour,12],[Rs.day,1],[Rs.day,2],[Rs.week,1],[Rs.month,1],[Rs.month,3],[Rs.year,1]],cl=[[lo("%Y"),Zt],[lo("%B"),function(n){return n.getMonth()}],[lo("%b %d"),function(n){return 1!=n.getDate()}],[lo("%a %d"),function(n){return n.getDay()&&1!=n.getDate()}],[lo("%I %p"),function(n){return n.getHours()}],[lo("%I:%M"),function(n){return n.getMinutes()}],[lo(":%S"),function(n){return n.getSeconds()}],[lo(".%L"),function(n){return n.getMilliseconds()}]],sl=Zo(cl);al.year=Rs.year,Rs.scale=function(){return Yo($o.scale.linear(),al,sl)};var ll={range:function(n,t,e){return $o.range(+n,+t,e).map(Io)}},fl=al.map(function(n){return[n[0].utc,n[1]]}),hl=[[Fo("%Y"),Zt],[Fo("%B"),function(n){return n.getUTCMonth()}],[Fo("%b %d"),function(n){return 1!=n.getUTCDate()}],[Fo("%a %d"),function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],[Fo("%I %p"),function(n){return n.getUTCHours()}],[Fo("%I:%M"),function(n){return n.getUTCMinutes()}],[Fo(":%S"),function(n){return n.getUTCSeconds()}],[Fo(".%L"),function(n){return n.getUTCMilliseconds()}]],gl=Zo(hl);return fl.year=Rs.year.utc,Rs.scale.utc=function(){return Yo($o.scale.linear(),fl,gl)},$o.text=dt(function(n){return n.responseText}),$o.json=function(n,t){return mt(n,"application/json",Vo,t)},$o.html=function(n,t){return mt(n,"text/html",Xo,t)},$o.xml=dt(function(n){return n.responseXML}),$o}(); \ No newline at end of file diff --git a/awx/ui/static/lib/loader.js/.bower.json b/awx/ui/static/lib/loader.js/.bower.json new file mode 100644 index 0000000000..ed825fe264 --- /dev/null +++ b/awx/ui/static/lib/loader.js/.bower.json @@ -0,0 +1,26 @@ +{ + "name": "loader.js", + "main": "loader.js", + "version": "1.0.1", + "homepage": "https://github.com/stefanpenner/loader.js", + "authors": [ + "Stefan Penner " + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "_release": "1.0.1", + "_resolution": { + "type": "version", + "tag": "1.0.1", + "commit": "ad35c820bb111fe6d68bb5c484e1a0e03748e298" + }, + "_source": "git://github.com/ember-cli/loader.js.git", + "_target": "1.0.1", + "_originalSource": "ember-cli/loader.js" +} \ No newline at end of file diff --git a/awx/ui/static/lib/loader.js/README.md b/awx/ui/static/lib/loader.js/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/static/lib/loader.js/bower.json b/awx/ui/static/lib/loader.js/bower.json new file mode 100644 index 0000000000..d90ee21eca --- /dev/null +++ b/awx/ui/static/lib/loader.js/bower.json @@ -0,0 +1,17 @@ +{ + "name": "loader.js", + "main": "loader.js", + "version": "0.0.0", + "homepage": "https://github.com/stefanpenner/loader.js", + "authors": [ + "Stefan Penner " + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/awx/ui/static/lib/loader.js/loader.js b/awx/ui/static/lib/loader.js/loader.js new file mode 100644 index 0000000000..0c9559c39b --- /dev/null +++ b/awx/ui/static/lib/loader.js/loader.js @@ -0,0 +1,110 @@ +var define, requireModule, require, requirejs; + +(function() { + + var _isArray; + if (!Array.isArray) { + _isArray = function (x) { + return Object.prototype.toString.call(x) === "[object Array]"; + }; + } else { + _isArray = Array.isArray; + } + + var registry = {}, seen = {}, state = {}; + var FAILED = false; + + define = function(name, deps, callback) { + + if (!_isArray(deps)) { + callback = deps; + deps = []; + } + + registry[name] = { + deps: deps, + callback: callback + }; + }; + + function reify(deps, name, seen) { + var length = deps.length; + var reified = new Array(length); + var dep; + var exports; + + for (var i = 0, l = length; i < l; i++) { + dep = deps[i]; + if (dep === 'exports') { + exports = reified[i] = seen; + } else { + reified[i] = require(resolve(dep, name)); + } + } + + return { + deps: reified, + exports: exports + }; + } + + requirejs = require = requireModule = function(name) { + if (state[name] !== FAILED && + seen.hasOwnProperty(name)) { + return seen[name]; + } + + if (!registry[name]) { + throw new Error('Could not find module ' + name); + } + + var mod = registry[name]; + var reified; + var module; + var loaded = false; + + seen[name] = { }; // placeholder for run-time cycles + + try { + reified = reify(mod.deps, name, seen[name]); + module = mod.callback.apply(this, reified.deps); + loaded = true; + } finally { + if (!loaded) { + state[name] = FAILED; + } + } + + return reified.exports ? seen[name] : (seen[name] = module); + }; + + function resolve(child, name) { + if (child.charAt(0) !== '.') { return child; } + + var parts = child.split('/'); + var nameParts = name.split('/'); + var parentBase; + + if (nameParts.length === 1) { + parentBase = nameParts; + } else { + parentBase = nameParts.slice(0, -1); + } + + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + + if (part === '..') { parentBase.pop(); } + else if (part === '.') { continue; } + else { parentBase.push(part); } + } + + return parentBase.join('/'); + } + + requirejs.entries = requirejs._eak_seen = registry; + requirejs.clear = function(){ + requirejs.entries = requirejs._eak_seen = registry = {}; + seen = state = {}; + }; +})(); diff --git a/awx/ui/static/partials/survey_maker.html b/awx/ui/static/partials/survey_maker.html index a550afe572..72398bc08f 100644 --- a/awx/ui/static/partials/survey_maker.html +++ b/awx/ui/static/partials/survey_maker.html @@ -1,5 +1,6 @@
    +
    diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index eb42d5303d..7e1de8279a 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -307,6 +307,11 @@