diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 5a825ba5c634..ce073116bbe7 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -1713,33 +1713,6 @@ tr td button i { } } -/* Activity Stream Widget */ - - #stream-container { - display: none; - border-radius: 8px; - z-index: 20; /* has to be greater than tree selector */ - - .nav-path { - margin-bottom: 15px; - margin-top: 5px; - } - - padding-left: 10px; - padding-right: 10px; - } - - #stream-content { - border: 1px solid @grey; - border-radius: 8px; - padding: 8px; - - h5 { - margin-top: 0; - margin-bottom: 20px; - } - } - /* job stdout */ #pre-container { diff --git a/awx/ui/client/src/activity-stream/activitystream.controller.js b/awx/ui/client/src/activity-stream/activitystream.controller.js new file mode 100644 index 000000000000..13d2ccecce89 --- /dev/null +++ b/awx/ui/client/src/activity-stream/activitystream.controller.js @@ -0,0 +1,21 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Activity Stream + * @description This controller controls the activity stream. +*/ +function activityStreamController($scope, Stream) { + + // Open the stream + Stream({ + scope: $scope + }); + +} + +export default ['$scope', 'Stream', activityStreamController]; diff --git a/awx/ui/client/src/activity-stream/activitystream.partial.html b/awx/ui/client/src/activity-stream/activitystream.partial.html new file mode 100644 index 000000000000..8c6263b11d2c --- /dev/null +++ b/awx/ui/client/src/activity-stream/activitystream.partial.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/awx/ui/client/src/activity-stream/activitystream.route.js b/awx/ui/client/src/activity-stream/activitystream.route.js new file mode 100644 index 000000000000..e334363e9c47 --- /dev/null +++ b/awx/ui/client/src/activity-stream/activitystream.route.js @@ -0,0 +1,17 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + import {templateUrl} from '../shared/template-url/template-url.factory'; + +export default { + name: 'activityStream', + route: '/activity_stream?target&id', + templateUrl: templateUrl('activity-stream/activitystream'), + controller: 'activityStreamController', + ncyBreadcrumb: { + label: "ACTIVITY STREAM" + }, +}; diff --git a/awx/ui/client/src/activity-stream/main.js b/awx/ui/client/src/activity-stream/main.js new file mode 100644 index 000000000000..1b3f169de24a --- /dev/null +++ b/awx/ui/client/src/activity-stream/main.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import activityStreamRoute from './activitystream.route'; +import activityStreamController from './activitystream.controller'; + +export default angular.module('activityStream', []) + .controller('activityStreamController', activityStreamController) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(activityStreamRoute); + }]); diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 4283b1943fb0..40f874cf6b4a 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -42,6 +42,7 @@ import moment from './shared/moment/main'; import templateUrl from './shared/template-url/main'; import adhoc from './adhoc/main'; import login from './login/main'; +import activityStream from './activity-stream/main'; import {JobDetailController} from './controllers/JobDetail'; import {JobStdoutController} from './controllers/JobStdout'; import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from './controllers/JobTemplates'; @@ -91,6 +92,7 @@ var tower = angular.module('Tower', [ templateUrl.name, adhoc.name, login.name, + activityStream.name, footer.name, 'templates', 'Utilities', @@ -209,6 +211,9 @@ var tower = angular.module('Tower', [ url: '/home', templateUrl: urlPrefix + 'partials/home.html', controller: Home, + data: { + activityStream: true + }, ncyBreadcrumb: { label: "DASHBOARD" }, @@ -241,6 +246,10 @@ var tower = angular.module('Tower', [ url: '/home/hosts?has_active_failures', templateUrl: urlPrefix + 'partials/subhome.html', controller: HomeHosts, + data: { + activityStream: true, + activityStreamTarget: 'host' + }, ncyBreadcrumb: { parent: 'dashboard', label: "HOSTS" @@ -361,6 +370,10 @@ var tower = angular.module('Tower', [ url: '/job_templates', templateUrl: urlPrefix + 'partials/job_templates.html', controller: JobTemplatesList, + data: { + activityStream: true, + activityStreamTarget: 'job_template' + }, ncyBreadcrumb: { label: "JOB TEMPLATES" }, @@ -390,6 +403,9 @@ var tower = angular.module('Tower', [ url: '/:template_id', templateUrl: urlPrefix + 'partials/job_templates.html', controller: JobTemplatesEdit, + data: { + activityStreamId: 'template_id' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -401,6 +417,10 @@ var tower = angular.module('Tower', [ url: '/job_templates/:id/schedules', templateUrl: urlPrefix + 'partials/schedule_detail.html', controller: ScheduleEditController, + data: { + activityStream: true, + activityStreamTarget: 'schedule' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -412,6 +432,10 @@ var tower = angular.module('Tower', [ url: '/projects', templateUrl: urlPrefix + 'partials/projects.html', controller: ProjectsList, + data: { + activityStream: true, + activityStreamTarget: 'project' + }, ncyBreadcrumb: { label: "PROJECTS" }, @@ -441,6 +465,9 @@ var tower = angular.module('Tower', [ url: '/:id', templateUrl: urlPrefix + 'partials/projects.html', controller: ProjectsEdit, + data: { + activityStreamId: 'id' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -452,6 +479,10 @@ var tower = angular.module('Tower', [ url: '/projects/:id/schedules', templateUrl: urlPrefix + 'partials/schedule_detail.html', controller: ScheduleEditController, + data: { + activityStream: true, + activityStreamTarget: 'schedule' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -485,6 +516,10 @@ var tower = angular.module('Tower', [ url: '/inventories', templateUrl: urlPrefix + 'partials/inventories.html', controller: InventoriesList, + data: { + activityStream: true, + activityStreamTarget: 'inventory' + }, ncyBreadcrumb: { label: "INVENTORIES" }, @@ -514,6 +549,9 @@ var tower = angular.module('Tower', [ url: '/:inventory_id', templateUrl: urlPrefix + 'partials/inventories.html', controller: InventoriesEdit, + data: { + activityStreamId: 'inventory_id' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -558,6 +596,10 @@ var tower = angular.module('Tower', [ url: '/organizations', templateUrl: urlPrefix + 'partials/organizations.html', controller: OrganizationsList, + data: { + activityStream: true, + activityStreamTarget: 'organization' + }, ncyBreadcrumb: { parent: function($scope) { $scope.$parent.$emit("ReloadOrgListView"); @@ -591,6 +633,9 @@ var tower = angular.module('Tower', [ url: '/:organization_id', templateUrl: urlPrefix + 'partials/organizations.crud.html', controller: OrganizationsEdit, + data: { + activityStreamId: 'organization_id' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -646,6 +691,10 @@ var tower = angular.module('Tower', [ url: '/teams', templateUrl: urlPrefix + 'partials/teams.html', controller: TeamsList, + data: { + activityStream: true, + activityStreamTarget: 'team' + }, ncyBreadcrumb: { parent: 'setup', label: 'TEAMS' @@ -676,6 +725,9 @@ var tower = angular.module('Tower', [ url: '/:team_id', templateUrl: urlPrefix + 'partials/teams.html', controller: TeamsEdit, + data: { + activityStreamId: 'team_id' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -775,6 +827,10 @@ var tower = angular.module('Tower', [ url: '/credentials', templateUrl: urlPrefix + 'partials/credentials.html', controller: CredentialsList, + data: { + activityStream: true, + activityStreamTarget: 'credential' + }, ncyBreadcrumb: { parent: 'setup', label: 'CREDENTIALS' @@ -805,6 +861,9 @@ var tower = angular.module('Tower', [ url: '/:credential_id', templateUrl: urlPrefix + 'partials/credentials.html', controller: CredentialsEdit, + data: { + activityStreamId: 'credential_id' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -816,6 +875,10 @@ var tower = angular.module('Tower', [ url: '/users', templateUrl: urlPrefix + 'partials/users.html', controller: UsersList, + data: { + activityStream: true, + activityStreamTarget: 'user' + }, ncyBreadcrumb: { parent: 'setup', label: 'USERS' @@ -846,6 +909,9 @@ var tower = angular.module('Tower', [ url: '/:user_id', templateUrl: urlPrefix + 'partials/users.html', controller: UsersEdit, + data: { + activityStreamId: 'user_id' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); @@ -925,9 +991,9 @@ var tower = angular.module('Tower', [ }]); }]) - .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'ClearScope', 'HideStream', 'Socket', + .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'ClearScope', 'Socket', 'LoadConfig', 'Store', 'ShowSocketHelp', 'AboutAnsibleHelp', 'pendoService', - function ($q, $compile, $cookieStore, $rootScope, $log, CheckLicense, $location, Authorization, LoadBasePaths, Timer, ClearScope, HideStream, Socket, + function ($q, $compile, $cookieStore, $rootScope, $log, CheckLicense, $location, Authorization, LoadBasePaths, Timer, ClearScope, Socket, LoadConfig, Store, ShowSocketHelp, AboutAnsibleHelp, pendoService) { @@ -1068,11 +1134,6 @@ var tower = angular.module('Tower', [ $location.replace($location.search('').$$url); } - // Before navigating away from current tab, make sure the primary view is visible - if ($('#stream-container').is(':visible')) { - HideStream(); - } - // remove any lingering intervals if ($rootScope.jobDetailInterval) { window.clearInterval($rootScope.jobDetailInterval); diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js index f7da02fcaa90..3a0f2ac876c0 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js +++ b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js @@ -6,15 +6,39 @@ export default restrict: 'E', templateUrl: templateUrl('bread-crumb/bread-crumb'), link: function(scope, element, attrs) { - scope.activityStreamActive = 0; - scope.toggleActivityStreamActive = function(){ - scope.activityStreamActive = !scope.activityStreamActive; - }; + var streamConfig = {}; + + scope.showActivityStreamButton = false; + + scope.openActivityStream = function() { + + var stateGoParams = {}; + + if(streamConfig && streamConfig.activityStream) { + if(streamConfig.activityStreamTarget) { + stateGoParams['target'] = streamConfig.activityStreamTarget; + } + if(streamConfig.activityStreamId) { + stateGoParams['id'] = $state.params[streamConfig.activityStreamId]; + } + } + + $state.go('activityStream', stateGoParams); + } + + scope.$on("$stateChangeSuccess", function updateActivityStreamButton(event, toState) { + + streamConfig = (toState && toState.data) ? toState.data : {}; + + if(streamConfig && streamConfig.activityStream) { + scope.showActivityStreamButton = true; + } + else { + scope.showActivityStreamButton = false; + } + }); - scope.isActive = function (path) { - return $state.is(path); - }; } }; }]; diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.partial.html b/awx/ui/client/src/bread-crumb/bread-crumb.partial.html index bef217bd0411..6cdae3acb23b 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.partial.html +++ b/awx/ui/client/src/bread-crumb/bread-crumb.partial.html @@ -7,8 +7,8 @@ data-trigger="hover" data-container="body" ng-class="{'BreadCrumb-menuLinkActive' : activityStreamActive}" - ng-if="isActive('dashboard')" - ng-click="toggleActivityStreamActive()"> + ng-if="showActivityStreamButton" + ng-click="openActivityStream()"> @@ -20,7 +20,7 @@ data-placement="left" data-trigger="hover" data-container="body" - ng-if="!isActive('dashboard')"> + ng-if="!showActivityStreamButton"> diff --git a/awx/ui/client/src/inventory-scripts/list/list.route.js b/awx/ui/client/src/inventory-scripts/list/list.route.js index 3c897c1321c7..6557534d3c9f 100644 --- a/awx/ui/client/src/inventory-scripts/list/list.route.js +++ b/awx/ui/client/src/inventory-scripts/list/list.route.js @@ -11,6 +11,10 @@ export default { route: '/inventory_scripts', templateUrl: templateUrl('inventory-scripts/list/list'), controller: 'inventoryScriptsListController', + data: { + activityStream: true, + activityStreamTarget: 'inventory_script' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); diff --git a/awx/ui/client/src/lists/Streams.js b/awx/ui/client/src/lists/Streams.js index 2b40c913765a..0cce80b7f9a5 100644 --- a/awx/ui/client/src/lists/Streams.js +++ b/awx/ui/client/src/lists/Streams.js @@ -12,6 +12,7 @@ export default name: 'activities', iterator: 'activity', editTitle: 'Activity Stream', + listTitle: 'Activity Stream', selectInstructions: '', index: false, hover: true, @@ -252,13 +253,6 @@ export default }, actions: { - close: { - mode: 'all', - awToolTip: "Close Activity Stream view", - ngClick: "closeStream()", - actionClass: 'btn List-buttonDefault', - buttonContent: 'CLOSE' - }, refresh: { mode: 'all', id: 'activity-stream-refresh-btn', diff --git a/awx/ui/client/src/management-jobs/list/list.route.js b/awx/ui/client/src/management-jobs/list/list.route.js index 0c2d100803e2..7301ebffe2a2 100644 --- a/awx/ui/client/src/management-jobs/list/list.route.js +++ b/awx/ui/client/src/management-jobs/list/list.route.js @@ -11,6 +11,10 @@ export default { route: '/management_jobs', templateUrl: templateUrl('management-jobs/list/list'), controller: 'managementJobsListController', + data: { + activityStream: true, + activityStreamTarget: 'management_job' + }, resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); diff --git a/awx/ui/client/src/management-jobs/schedule/schedule.route.js b/awx/ui/client/src/management-jobs/schedule/schedule.route.js index 060825b07576..837d6dfb86b7 100644 --- a/awx/ui/client/src/management-jobs/schedule/schedule.route.js +++ b/awx/ui/client/src/management-jobs/schedule/schedule.route.js @@ -11,6 +11,10 @@ export default { route: '/management_jobs/:management_job_id/schedules', templateUrl: templateUrl('management-jobs/schedule/schedule'), controller: 'managementJobsScheduleController', + data: { + activityStream: true, + activityStreamTarget: 'schedule' + }, params: {management_job: null}, resolve: { features: ['FeaturesService', function(FeaturesService) { diff --git a/awx/ui/client/src/partials/home.html b/awx/ui/client/src/partials/home.html index 6ff4feee70c6..491a728e6433 100644 --- a/awx/ui/client/src/partials/home.html +++ b/awx/ui/client/src/partials/home.html @@ -13,15 +13,6 @@ icon-name="refresh" toolbar="true"> - diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index 64994189931e..aa435b7c42c4 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -9,6 +9,7 @@ export default function($stateProvider){ templateUrl: state.templateUrl, resolve: state.resolve, params: state.params, + data: state.data, ncyBreadcrumb: state.ncyBreadcrumb }); } diff --git a/awx/ui/client/src/widgets/Stream.js b/awx/ui/client/src/widgets/Stream.js index b793d0bab105..af44f1daf2ab 100644 --- a/awx/ui/client/src/widgets/Stream.js +++ b/awx/ui/client/src/widgets/Stream.js @@ -37,65 +37,6 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti } ]) -.factory('ShowStream', ['setStreamHeight', 'Authorization', - function (setStreamHeight) { - return function () { - // Slide in the Stream widget - - // Make some style/position adjustments adjustments - var stream = $('#stream-container'); - stream.css({ - position: 'absolute', - top: 0, - left: 0, - width: '100%', - 'min-height': '100%', - 'background-color': '#FFF' - }); - - setStreamHeight(); - - // Slide in stream - stream.show('slide', { - 'direction': 'left' - }, { - 'duration': 500, - 'queue': false - }); - - }; - } -]) - -.factory('HideStream', [ - function () { - return function () { - // Remove the stream widget - - var stream = $('#stream-container'); - stream.hide('slide', { - 'direction': 'left' - }, { - 'duration': 500, - 'queue': false - }); - - // Completely destroy the container so we don't experience random flashes of it later. - // There was some sort of weirdness with the tab 'show' causing the stream to slide in when - // a tab was clicked, after the stream had been hidden. Seemed like timing- wait long enough - // before clicking a tab, and it would not happen. - setTimeout(function () { - stream.detach(); - stream.empty(); - stream.unbind(); - $('#main-view').css({ - 'min-height': 0 - }); //let the parent height go back to normal - }, 500); - }; - } -]) - .factory('FixUrl', [ function () { return function (u) { @@ -335,11 +276,11 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti } ]) -.factory('Stream', ['$rootScope', '$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'StreamList', 'SearchInit', - 'PaginateInit', 'generateList', 'FormatDate', 'ShowStream', 'HideStream', 'BuildDescription', 'FixUrl', 'BuildUrl', +.factory('Stream', ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'StreamList', 'SearchInit', + 'PaginateInit', 'generateList', 'FormatDate', 'BuildDescription', 'FixUrl', 'BuildUrl', 'ShowDetail', 'setStreamHeight', 'Find', 'Store', - function ($rootScope, $location, Rest, GetBasePath, ProcessErrors, Wait, StreamList, SearchInit, PaginateInit, GenerateList, - FormatDate, ShowStream, HideStream, BuildDescription, FixUrl, BuildUrl, ShowDetail, setStreamHeight, + function ($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors, Wait, StreamList, SearchInit, PaginateInit, GenerateList, + FormatDate, BuildDescription, FixUrl, BuildUrl, ShowDetail, setStreamHeight, Find, Store) { return function (params) { @@ -361,63 +302,25 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti if (url) { defaultUrl = url; } else { - if ($location.path() !== '/home') { - // Restrict what we're looking at based on the path - type = (base === 'inventories') ? 'inventory' : base.replace(/s$/, ''); - paths = $location.path().split('/'); - paths.splice(0, 1); - if (paths.length > 1 && /^\d+/.test(paths[paths.length - 1])) { - type = paths[paths.length - 2]; - type = (type === 'inventories') ? 'inventory' : type.replace(/s$/, ''); - //defaultUrl += '?object1=' + type + '&object1__id=' + - defaultUrl += '?' + type + '__id=' + paths[paths.length - 1]; - } else if (paths.length > 1) { - type = paths[paths.length - 1]; - type = (type === 'inventories') ? 'inventory' : type.replace(/s$/, ''); - defaultUrl += '?or__object1=' + type + '&or__object2=' + type; - } else { - defaultUrl += '?or__object1=' + type + '&or__object2=' + type; + + if($state.params && $state.params.target) { + if($state.params.id) { + // We have a type and an ID + defaultUrl += '?' + $state.params.target + '__id=' + $state.params.id; + } + else { + // We just have a type + defaultUrl += '?or__object1=' + $state.params.target + '&or__object2=' + $state.params.target; } } } - // Add a container for the stream widget - $('#main-view').append("
"); - - ShowStream(); - // Generate the list view.inject(list, { mode: 'edit', id: 'stream-content', searchSize: 'col-lg-3', secondWidget: true, activityStream: true, scope: scope }); // descriptive title describing what AS is showing scope.streamTitle = (params && params.title) ? params.title : null; - scope.closeStream = function (inUrl) { - HideStream(); - if (scope.searchCleanup) { - scope.searchCleanup(); - } - // Restore prior search state - if (PreviousSearchParams) { - SearchInit({ - scope: parent_scope, - set: PreviousSearchParams.set, - list: PreviousSearchParams.list, - url: PreviousSearchParams.defaultUrl, - iterator: PreviousSearchParams.iterator, - sort_order: PreviousSearchParams.sort_order, - setWidgets: false - }); - } - if (inUrl) { - $location.path(inUrl); - } - else if (onClose) { - parent_scope.$emit(onClose); - } - scope.$destroy(); - }; - scope.refreshStream = function () { scope.search(list.iterator); };