mirror of
https://github.com/ansible/awx.git
synced 2026-01-16 12:20:45 -03:30
Merge pull request #5599 from mabashian/cleanup-widgets
Cleanup widgets
This commit is contained in:
commit
6604a36bc9
@ -0,0 +1,78 @@
|
||||
export default
|
||||
function BuildAnchor($log, $filter) {
|
||||
// Returns a full <a href=''>resource_name</a> HTML string if link can be derived from supplied context
|
||||
// returns name of resource if activity stream object doesn't contain enough data to build a UI url
|
||||
// arguments are: a summary_field object, a resource type, an activity stream object
|
||||
return function (obj, resource, activity) {
|
||||
var url = '/#/';
|
||||
// try/except pattern asserts that:
|
||||
// if we encounter a case where a UI url can't or shouldn't be generated, just supply the name of the resource
|
||||
try {
|
||||
// catch-all case to avoid generating urls if a resource has been deleted
|
||||
// if a resource still exists, it'll be serialized in the activity's summary_fields
|
||||
if (!activity.summary_fields[resource]){
|
||||
throw {name : 'ResourceDeleted', message: 'The referenced resource no longer exists'};
|
||||
}
|
||||
switch (resource) {
|
||||
case 'custom_inventory_script':
|
||||
url += 'inventory_scripts/' + obj.id + '/';
|
||||
break;
|
||||
case 'group':
|
||||
if (activity.operation === 'create' || activity.operation === 'delete'){
|
||||
// the API formats the changes.inventory field as str 'myInventoryName-PrimaryKey'
|
||||
var inventory_id = _.last(activity.changes.inventory.split('-'));
|
||||
url += 'inventories/' + inventory_id + '/manage?group=' + activity.changes.id;
|
||||
}
|
||||
else {
|
||||
url += 'inventories/' + activity.summary_fields.inventory[0].id + '/manage?group=' + (activity.changes.id || activity.changes.object1_pk);
|
||||
}
|
||||
break;
|
||||
case 'host':
|
||||
url += 'home/hosts/' + obj.id;
|
||||
break;
|
||||
case 'job':
|
||||
url += 'jobs/' + obj.id;
|
||||
break;
|
||||
case 'inventory':
|
||||
url += 'inventories/' + obj.id + '/';
|
||||
break;
|
||||
case 'schedule':
|
||||
// schedule urls depend on the resource they're associated with
|
||||
if (activity.summary_fields.job_template){
|
||||
url += 'job_templates/' + activity.summary_fields.job_template.id + '/schedules/' + obj.id;
|
||||
}
|
||||
else if (activity.summary_fields.project){
|
||||
url += 'projects/' + activity.summary_fields.project.id + '/schedules/' + obj.id;
|
||||
}
|
||||
else if (activity.summary_fields.system_job_template){
|
||||
url += 'management_jobs/' + activity.summary_fields.system_job_template.id + '/schedules/edit/' + obj.id;
|
||||
}
|
||||
// urls for inventory sync schedules currently depend on having an inventory id and group id
|
||||
else {
|
||||
throw {name : 'NotImplementedError', message : 'activity.summary_fields to build this url not implemented yet'};
|
||||
}
|
||||
break;
|
||||
case 'notification_template':
|
||||
url += `notification_templates/${obj.id}`;
|
||||
break;
|
||||
case 'role':
|
||||
throw {name : 'NotImplementedError', message : 'role object management is not consolidated to a single UI view'};
|
||||
case 'job_template':
|
||||
url += `templates/job_template/${obj.id}`;
|
||||
break;
|
||||
case 'workflow_job_template':
|
||||
url += `templates/workflow_job_template/${obj.id}`;
|
||||
break;
|
||||
default:
|
||||
url += resource + 's/' + obj.id + '/';
|
||||
}
|
||||
return ' <a href=\"' + url + '\"> ' + $filter('sanitize')(obj.name || obj.username) + ' </a> ';
|
||||
}
|
||||
catch(err){
|
||||
$log.debug(err);
|
||||
return ' ' + $filter('sanitize')(obj.name || obj.username || '') + ' ';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BuildAnchor.$inject = ['$log', '$filter'];
|
||||
@ -0,0 +1,126 @@
|
||||
export default
|
||||
function BuildDescription(BuildAnchor, $log, i18n) {
|
||||
return function (activity) {
|
||||
|
||||
var pastTense = function(operation){
|
||||
return (/e$/.test(activity.operation)) ? operation + 'd ' : operation + 'ed ';
|
||||
};
|
||||
// convenience method to see if dis+association operation involves 2 groups
|
||||
// the group cases are slightly different because groups can be dis+associated into each other
|
||||
var isGroupRelationship = function(activity){
|
||||
return activity.object1 === 'group' && activity.object2 === 'group' && activity.summary_fields.group.length > 1;
|
||||
};
|
||||
|
||||
// Activity stream objects will outlive the resources they reference
|
||||
// in that case, summary_fields will not be available - show generic error text instead
|
||||
try {
|
||||
activity.description = pastTense(activity.operation);
|
||||
switch(activity.object_association){
|
||||
// explicit role dis+associations
|
||||
case 'role':
|
||||
// object1 field is resource targeted by the dis+association
|
||||
// object2 field is the resource the role is inherited from
|
||||
// summary_field.role[0] contains ref info about the role
|
||||
switch(activity.operation){
|
||||
// expected outcome: "disassociated <object2> role_name from <object1>"
|
||||
case 'disassociate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' from ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
// expected outcome: "associated <object2> role_name to <object1>"
|
||||
case 'associate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' to ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// inherited role dis+associations (logic identical to case 'role')
|
||||
case 'parents':
|
||||
// object1 field is resource targeted by the dis+association
|
||||
// object2 field is the resource the role is inherited from
|
||||
// summary_field.role[0] contains ref info about the role
|
||||
switch(activity.operation){
|
||||
// expected outcome: "disassociated <object2> role_name from <object1>"
|
||||
case 'disassociate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) +
|
||||
'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
// expected outcome: "associated <object2> role_name to <object1>"
|
||||
case 'associate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) +
|
||||
'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// CRUD operations / resource on resource dis+associations
|
||||
default:
|
||||
switch(activity.operation){
|
||||
// expected outcome: "disassociated <object2> from <object1>"
|
||||
case 'disassociate' :
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) +
|
||||
'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else {
|
||||
activity.description += activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) +
|
||||
'from ' + activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
// expected outcome "associated <object2> to <object1>"
|
||||
case 'associate':
|
||||
// groups are the only resource that can be associated/disassociated into each other
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) +
|
||||
'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity);
|
||||
}
|
||||
else {
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity) +
|
||||
'to ' + activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity);
|
||||
break;
|
||||
// expected outcome: "operation <object1>"
|
||||
case 'update':
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
break;
|
||||
case 'create':
|
||||
activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(err){
|
||||
$log.debug(err);
|
||||
activity.description = i18n._('Event summary not available');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BuildDescription.$inject = ['BuildAnchor', '$log', 'i18n'];
|
||||
@ -0,0 +1,39 @@
|
||||
export default
|
||||
function ShowDetail($filter, $rootScope, Rest, Alert, GenerateForm, ProcessErrors, GetBasePath, FormatDate, ActivityDetailForm, Empty, Find) {
|
||||
return function (params, scope) {
|
||||
|
||||
var activity_id = params.activity_id,
|
||||
activity = Find({ list: params.scope.activities, key: 'id', val: activity_id }),
|
||||
element;
|
||||
|
||||
if (activity) {
|
||||
|
||||
// Grab our element out of the dom
|
||||
element = angular.element(document.getElementById('stream-detail-modal'));
|
||||
|
||||
// Grab the modal's scope so that we can set a few variables
|
||||
scope = element.scope();
|
||||
|
||||
scope.changes = activity.changes;
|
||||
scope.user = ((activity.summary_fields.actor) ? activity.summary_fields.actor.username : 'system') +
|
||||
' on ' + $filter('longDate')(activity.timestamp);
|
||||
scope.operation = activity.description;
|
||||
scope.header = "Event " + activity.id;
|
||||
|
||||
// Open the modal
|
||||
$('#stream-detail-modal').modal({
|
||||
show: true,
|
||||
backdrop: 'static',
|
||||
keyboard: true
|
||||
});
|
||||
|
||||
if (!scope.$$phase) {
|
||||
scope.$digest();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
ShowDetail.$inject = ['$filter', '$rootScope', 'Rest', 'Alert', 'GenerateForm', 'ProcessErrors', 'GetBasePath', 'FormatDate',
|
||||
'ActivityDetailForm', 'Empty', 'Find'];
|
||||
@ -0,0 +1,54 @@
|
||||
export default
|
||||
function Stream($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors,
|
||||
Wait, StreamList, GenerateList, FormatDate,
|
||||
BuildDescription, ShowDetail) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope;
|
||||
|
||||
$rootScope.flashMessage = null;
|
||||
|
||||
// descriptive title describing what AS is showing
|
||||
scope.streamTitle = (params && params.title) ? params.title : null;
|
||||
|
||||
scope.refreshStream = function () {
|
||||
$state.go('.', null, {reload: true});
|
||||
};
|
||||
|
||||
scope.showDetail = function (id) {
|
||||
ShowDetail({
|
||||
scope: scope,
|
||||
activity_id: id
|
||||
});
|
||||
};
|
||||
|
||||
if(scope.activities && scope.activities.length > 0) {
|
||||
buildUserAndDescription();
|
||||
}
|
||||
|
||||
scope.$watch('activities', function(){
|
||||
// Watch for future update to scope.activities (like page change, column sort, search, etc)
|
||||
buildUserAndDescription();
|
||||
});
|
||||
|
||||
function buildUserAndDescription(){
|
||||
scope.activities.forEach(function(activity, i) {
|
||||
// build activity.user
|
||||
if (scope.activities[i].summary_fields.actor) {
|
||||
scope.activities[i].user = "<a href=\"/#/users/" + scope.activities[i].summary_fields.actor.id + "\">" +
|
||||
scope.activities[i].summary_fields.actor.username + "</a>";
|
||||
} else {
|
||||
scope.activities[i].user = 'system';
|
||||
}
|
||||
// build description column / action text
|
||||
BuildDescription(scope.activities[i]);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
Stream.$inject = ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath',
|
||||
'ProcessErrors', 'Wait', 'StreamList', 'generateList', 'FormatDate', 'BuildDescription',
|
||||
'ShowDetail'];
|
||||
@ -6,14 +6,20 @@
|
||||
|
||||
import activityStreamRoute from './activitystream.route';
|
||||
import activityStreamController from './activitystream.controller';
|
||||
|
||||
import streamDropdownNav from './streamDropdownNav/stream-dropdown-nav.directive';
|
||||
|
||||
import streamDetailModal from './streamDetailModal/main';
|
||||
import BuildAnchor from './factories/build-anchor.factory';
|
||||
import BuildDescription from './factories/build-description.factory';
|
||||
import ShowDetail from './factories/show-detail.factory';
|
||||
import Stream from './factories/stream.factory';
|
||||
|
||||
export default angular.module('activityStream', [streamDetailModal.name])
|
||||
.controller('activityStreamController', activityStreamController)
|
||||
.directive('streamDropdownNav', streamDropdownNav)
|
||||
.factory('BuildAnchor', BuildAnchor)
|
||||
.factory('BuildDescription', BuildDescription)
|
||||
.factory('ShowDetail', ShowDetail)
|
||||
.factory('Stream', Stream)
|
||||
.run(['$stateExtender', function($stateExtender) {
|
||||
$stateExtender.addState(activityStreamRoute);
|
||||
}]);
|
||||
|
||||
@ -39,7 +39,6 @@ if ($basePath) {
|
||||
// Modules
|
||||
import './helpers';
|
||||
import './lists';
|
||||
import './widgets';
|
||||
import './filters';
|
||||
import portalMode from './portal-mode/main';
|
||||
import systemTracking from './system-tracking/main';
|
||||
@ -179,7 +178,6 @@ var tower = angular.module('Tower', [
|
||||
'md5Helper',
|
||||
'SelectionHelper',
|
||||
'HostGroupsFormDefinition',
|
||||
'StreamWidget',
|
||||
'JobsHelper',
|
||||
'CredentialsHelper',
|
||||
'StreamListDefinition',
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import "./widgets/InventorySyncStatus";
|
||||
import "./widgets/JobStatus";
|
||||
import "./widgets/ObjectCount";
|
||||
import "./widgets/SCMSyncStatus";
|
||||
import "./widgets/Stream";
|
||||
@ -1,115 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:InventorySyncStatus
|
||||
* @description
|
||||
* InventorySyncStatus.js
|
||||
*
|
||||
* Dashboard widget showing object counts and license availability.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
angular.module('InventorySyncStatusWidget', ['RestServices', 'Utilities'])
|
||||
.factory('InventorySyncStatus', ['$rootScope', '$compile', function ($rootScope, $compile) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
target = params.target,
|
||||
dashboard = params.dashboard,
|
||||
html, group_total, group_fail, element, src;
|
||||
|
||||
html = "<div class=\"panel panel-default\">\n";
|
||||
html += "<div class=\"panel-heading\">Inventory Sync Status</div>\n";
|
||||
html += "<div class=\"panel-body\">\n";
|
||||
html += "<table class=\"table table-condensed table-hover\">\n";
|
||||
html += "<thead>\n";
|
||||
html += "<tr>\n";
|
||||
html += "<th class=\"col-md-4 col-lg-3\"></th>\n";
|
||||
html += "<th class=\"col-md-2 col-lg-1 text-right\">Failed</th>\n";
|
||||
html += "<th class=\"col-md-2 col-lg-1 text-right\">Total</th>\n";
|
||||
html += "</tr>\n";
|
||||
html += "</thead>\n";
|
||||
html += "<tbody>\n";
|
||||
|
||||
function makeRow(params) {
|
||||
var label = params.label,
|
||||
count = params.count,
|
||||
fail = params.fail,
|
||||
link = params.link,
|
||||
fail_link = params.fail_link,
|
||||
html = "<tr>\n";
|
||||
html += "<td><a href=\"" + link + "\"";
|
||||
html += (label === 'Hosts' || label === 'Groups') ? " class=\"pad-left-sm\" " : "";
|
||||
html += ">" + label + "</a></td>\n";
|
||||
html += "<td class=\"";
|
||||
html += (fail > 0) ? 'failed-column' : 'zero-column';
|
||||
html += " text-right\">";
|
||||
html += "<a href=\"" + fail_link + "\">" + fail + "</a>";
|
||||
html += "</td>\n";
|
||||
html += "<td class=\"text-right\">";
|
||||
html += "<a href=\"" + link + "\">" + count + "</a>";
|
||||
html += "</td></tr>\n";
|
||||
return html;
|
||||
}
|
||||
|
||||
html += makeRow({
|
||||
label: 'Inventories',
|
||||
count: (dashboard.inventories && dashboard.inventories.total_with_inventory_source) ?
|
||||
dashboard.inventories.total_with_inventory_source : 0,
|
||||
fail: (dashboard.inventories && dashboard.inventories.inventory_failed) ? dashboard.inventories.inventory_failed : 0,
|
||||
link: '/#/inventories/?has_inventory_sources=true',
|
||||
fail_link: '/#/inventories/?inventory_sources_with_failures=true'
|
||||
});
|
||||
|
||||
group_total = 0;
|
||||
group_fail = 0;
|
||||
if (dashboard.inventory_sources) {
|
||||
for (src in dashboard.inventory_sources) {
|
||||
group_total += (dashboard.inventory_sources[src].total) ? dashboard.inventory_sources[src].total : 0;
|
||||
group_fail += (dashboard.inventory_sources[src].failed) ? dashboard.inventory_sources[src].failed : 0;
|
||||
}
|
||||
}
|
||||
|
||||
html += makeRow({
|
||||
label: 'Groups',
|
||||
count: group_total,
|
||||
fail: group_fail,
|
||||
link: '/#/home/groups/?has_external_source=true',
|
||||
fail_link: '/#/home/groups/?status=failed'
|
||||
});
|
||||
|
||||
// Each inventory source
|
||||
for (src in dashboard.inventory_sources) {
|
||||
if (dashboard.inventory_sources[src].total) {
|
||||
html += makeRow({
|
||||
label: dashboard.inventory_sources[src].label,
|
||||
count: (dashboard.inventory_sources[src].total) ? dashboard.inventory_sources[src].total : 0,
|
||||
fail: (dashboard.inventory_sources[src].failed) ? dashboard.inventory_sources[src].failed : 0,
|
||||
link: '/#/home/groups/?source=' + src,
|
||||
fail_link: '/#/home/groups/?status=failed&source=' + src
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
html += "</tbody>\n";
|
||||
html += "</table>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
|
||||
element = angular.element(document.getElementById(target));
|
||||
element.html(html);
|
||||
$compile(element)(scope);
|
||||
scope.$emit('WidgetLoaded');
|
||||
|
||||
};
|
||||
}
|
||||
]);
|
||||
@ -1,107 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:JobStatus
|
||||
* @description
|
||||
* JobStatus.js
|
||||
*
|
||||
* Dashboard widget showing object counts and license availability.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
angular.module('JobStatusWidget', ['RestServices', 'Utilities'])
|
||||
.factory('JobStatus', ['$rootScope', '$compile', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
|
||||
function ($rootScope, $compile) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
target = params.target,
|
||||
dashboard = params.dashboard,
|
||||
html = '', element;
|
||||
|
||||
html = "<div class=\"panel panel-default\">\n";
|
||||
html += "<div class=\"panel-heading\">Job Status</div>\n";
|
||||
html += "<div class=\"panel-body\">\n";
|
||||
html += "<table class=\"table table-condensed table-hover\">\n";
|
||||
html += "<thead>\n";
|
||||
html += "<tr>\n";
|
||||
html += "<th class=\"col-md-4 col-lg-3\"></th>\n";
|
||||
html += "<th class=\"col-md-2 col-lg-1 text-right\">Failed</th>\n";
|
||||
html += "<th class=\"col-md-2 col-lg-1 text-right\">Total</th>\n";
|
||||
html += "</tr>\n";
|
||||
html += "</thead>\n";
|
||||
html += "<tbody>\n";
|
||||
|
||||
function makeRow(params) {
|
||||
var html = '',
|
||||
label = params.label,
|
||||
link = params.link,
|
||||
fail_link = params.fail_link,
|
||||
count = params.count,
|
||||
fail = params.fail;
|
||||
html += "<tr>\n";
|
||||
html += "<td><a href=\"" + link + "\"";
|
||||
html += (label === 'Hosts' || label === 'Groups') ? " class=\"pad-left-sm\" " : "";
|
||||
html += ">" + label + "</a></td>\n";
|
||||
html += "<td class=\"";
|
||||
html += (fail > 0) ? 'failed-column' : 'zero-column';
|
||||
html += " text-right\">";
|
||||
html += "<a href=\"" + fail_link + "\">" + fail + "</a>";
|
||||
html += "</td>\n";
|
||||
html += "<td class=\"text-right\">";
|
||||
html += "<a href=\"" + link + "\" >" + count + "</a>";
|
||||
html += "</td></tr>\n";
|
||||
return html;
|
||||
}
|
||||
|
||||
html += makeRow({
|
||||
label: 'Jobs',
|
||||
link: '/#/jobs',
|
||||
count: (dashboard.jobs && dashboard.jobs.total) ? dashboard.jobs.total : 0,
|
||||
fail: (dashboard.jobs && dashboard.jobs.failed) ? dashboard.jobs.failed : 0,
|
||||
fail_link: '/#/jobs/?status=failed'
|
||||
});
|
||||
html += makeRow({
|
||||
label: 'Inventories',
|
||||
link: '/#/inventories',
|
||||
count: (dashboard.inventories && dashboard.inventories.total) ? dashboard.inventories.total : 0,
|
||||
fail: (dashboard.inventories && dashboard.inventories.job_failed) ? dashboard.inventories.job_failed : 0,
|
||||
fail_link: '/#/inventories/?has_active_failures=true'
|
||||
});
|
||||
html += makeRow({
|
||||
label: 'Groups',
|
||||
link: '/#/home/groups',
|
||||
count: (dashboard.groups && dashboard.groups.total) ? dashboard.groups.total : 0,
|
||||
fail: (dashboard.groups && dashboard.groups.job_failed) ? dashboard.groups.job_failed : 0,
|
||||
fail_link: '/#/home/groups/?has_active_failures=true'
|
||||
});
|
||||
html += makeRow({
|
||||
label: 'Hosts',
|
||||
link: '/#/home/hosts',
|
||||
count: (dashboard.hosts && dashboard.hosts.total) ? dashboard.hosts.total : 0,
|
||||
fail: (dashboard.hosts && dashboard.hosts.failed) ? dashboard.hosts.failed : 0,
|
||||
fail_link: '/#/home/hosts/?has_active_failures=true'
|
||||
});
|
||||
|
||||
html += "</tbody>\n";
|
||||
html += "</table>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
|
||||
element = angular.element(document.getElementById(target));
|
||||
element.html(html);
|
||||
$compile(element)(scope);
|
||||
scope.$emit('WidgetLoaded');
|
||||
|
||||
};
|
||||
}
|
||||
]);
|
||||
@ -1,78 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:ObjectCount
|
||||
* @description
|
||||
* ObjectCount.js
|
||||
*
|
||||
* Dashboard widget showing object counts and license availability.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
angular.module('ObjectCountWidget', ['RestServices', 'Utilities'])
|
||||
.factory('ObjectCount', ['$rootScope', '$compile', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
|
||||
function ($rootScope, $compile) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
target = params.target,
|
||||
dashboard = params.dashboard,
|
||||
keys = ['organizations', 'users', 'teams', 'credentials', 'projects', 'inventories', 'groups', 'hosts',
|
||||
'job_templates', 'jobs'
|
||||
],
|
||||
i, html, element;
|
||||
|
||||
html = "<div class=\"panel panel-default\">\n";
|
||||
html += "<div class=\"panel-heading\">System Summary</div>\n";
|
||||
html += "<div class=\"panel-body\">\n";
|
||||
html += "<table class=\"table table-condensed table-hover\">\n";
|
||||
html += "<thead>\n";
|
||||
html += "<tr>\n";
|
||||
html += "<th class=\"col-md-5 col-lg-4\"></th>\n";
|
||||
html += "<th class=\"col-md-1 col-lg-1 text-right\">Total</th>\n";
|
||||
html += "</tr>\n";
|
||||
html += "</thead>\n";
|
||||
html += "<tbody>\n";
|
||||
|
||||
function makeRow(params) {
|
||||
var html = '',
|
||||
label = params.label,
|
||||
link = params.link,
|
||||
count = params.count;
|
||||
html += "<tr>\n";
|
||||
html += "<td class=\"capitalize\"><a href=\"" + link + "\"";
|
||||
html += (label === 'hosts' || label === 'groups') ? " class=\"pad-left-sm\" " : "";
|
||||
html += ">" + label.replace(/\_/g, ' ') + "</a></td>\n";
|
||||
html += "<td class=\"text-right\">";
|
||||
html += "<a href=\"" + link + "\" >" + count + "</a>";
|
||||
html += "</td></tr>\n";
|
||||
return html;
|
||||
}
|
||||
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
html += makeRow({
|
||||
label: keys[i],
|
||||
link: '/#/' + ((keys[i] === 'hosts' || keys[i] === 'groups') ? 'home/' + keys[i] : keys[i]),
|
||||
count: (dashboard[keys[i]] && dashboard[keys[i]].total) ? dashboard[keys[i]].total : 0
|
||||
});
|
||||
}
|
||||
|
||||
html += "</tbody>\n";
|
||||
html += "</table>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
element = angular.element(document.getElementById(target));
|
||||
element.html(html);
|
||||
$compile(element)(scope);
|
||||
scope.$emit('WidgetLoaded');
|
||||
};
|
||||
}
|
||||
]);
|
||||
@ -1,108 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:SCMSyncStatus
|
||||
* @description
|
||||
* SCMSyncStatus.js
|
||||
*
|
||||
* Dashboard widget showing object counts and license availability.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
angular.module('SCMSyncStatusWidget', ['RestServices', 'Utilities'])
|
||||
.factory('SCMSyncStatus', ['$rootScope', '$compile',
|
||||
function ($rootScope, $compile) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
target = params.target,
|
||||
dashboard = params.dashboard,
|
||||
i, html, total_count, element, type, labelList;
|
||||
|
||||
html = "<div class=\"panel panel-default\">\n";
|
||||
html += "<div class=\"panel-heading\">Project SCM Status</div>\n";
|
||||
html += "<div class=\"panel-body\">\n";
|
||||
html += "<table class=\"table table-condensed table-hover\">\n";
|
||||
html += "<thead>\n";
|
||||
html += "<tr>\n";
|
||||
html += "<th class=\"col-md-4 col-lg-3\"></th>\n";
|
||||
html += "<th class=\"col-md-2 col-lg-1 text-right\">Failed</th>\n";
|
||||
html += "<th class=\"col-md-2 col-lg-1 text-right\">Total</th>\n";
|
||||
html += "</tr>\n";
|
||||
html += "</thead>\n";
|
||||
html += "<tbody>\n";
|
||||
|
||||
function makeRow(params) {
|
||||
var html = '',
|
||||
label = params.label,
|
||||
link = params.link,
|
||||
fail_link = params.fail_link,
|
||||
count = params.count,
|
||||
fail = params.fail;
|
||||
html += "<tr>\n";
|
||||
html += "<td><a href=\"" + link + "\">" + label + "</a></td>\n";
|
||||
html += "<td class=\"";
|
||||
html += (fail > 0) ? 'failed-column' : 'zero-column';
|
||||
html += " text-right\">";
|
||||
html += "<a href=\"" + fail_link + "\">" + fail + "</a>";
|
||||
html += "</td>\n";
|
||||
html += "<td class=\"text-right\">";
|
||||
html += "<a href=\"" + link + "\" >" + count + "</a>";
|
||||
html += "</td></tr>\n";
|
||||
return html;
|
||||
}
|
||||
|
||||
total_count = 0;
|
||||
if (dashboard.scm_types) {
|
||||
for (type in dashboard.scm_types) {
|
||||
total_count += (dashboard.scm_types[type].total) ? dashboard.scm_types[type].total : 0;
|
||||
}
|
||||
}
|
||||
|
||||
html += makeRow({
|
||||
label: 'Projects',
|
||||
link: '/#/projects',
|
||||
count: total_count,
|
||||
fail: (dashboard.projects && dashboard.projects.failed) ? dashboard.projects.failed : 0,
|
||||
fail_link: '/#/projects/?status=failed'
|
||||
});
|
||||
|
||||
labelList = [];
|
||||
for (type in dashboard.scm_types) {
|
||||
labelList.push(type);
|
||||
}
|
||||
labelList.sort();
|
||||
for (i = 0; i < labelList.length; i++) {
|
||||
type = labelList[i];
|
||||
if (dashboard.scm_types[type].total) {
|
||||
html += makeRow({
|
||||
label: dashboard.scm_types[type].label,
|
||||
link: '/#/projects/?scm_type=' + type,
|
||||
count: (dashboard.scm_types[type].total) ? dashboard.scm_types[type].total : 0,
|
||||
fail: (dashboard.scm_types[type].failed) ? dashboard.scm_types[type].failed : 0,
|
||||
fail_link: '/#/projects/?scm_type=' + type + '&status=failed'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
html += "</tbody>\n";
|
||||
html += "</table>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
|
||||
element = angular.element(document.getElementById(target));
|
||||
element.html(html);
|
||||
$compile(element)(scope);
|
||||
scope.$emit('WidgetLoaded');
|
||||
|
||||
};
|
||||
}
|
||||
]);
|
||||
@ -1,317 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name widgets.function:Stream
|
||||
* @description
|
||||
* Stream.js
|
||||
*
|
||||
* Activity stream widget that can be called from anywhere
|
||||
*
|
||||
*/
|
||||
|
||||
import listGenerator from '../shared/list-generator/main';
|
||||
|
||||
|
||||
angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefinition', listGenerator.name, 'StreamWidget',
|
||||
])
|
||||
|
||||
.factory('BuildAnchor', [ '$log', '$filter',
|
||||
// Returns a full <a href=''>resource_name</a> HTML string if link can be derived from supplied context
|
||||
// returns name of resource if activity stream object doesn't contain enough data to build a UI url
|
||||
// arguments are: a summary_field object, a resource type, an activity stream object
|
||||
function ($log, $filter) {
|
||||
return function (obj, resource, activity) {
|
||||
var url = '/#/';
|
||||
// try/except pattern asserts that:
|
||||
// if we encounter a case where a UI url can't or shouldn't be generated, just supply the name of the resource
|
||||
try {
|
||||
// catch-all case to avoid generating urls if a resource has been deleted
|
||||
// if a resource still exists, it'll be serialized in the activity's summary_fields
|
||||
if (!activity.summary_fields[resource]){
|
||||
throw {name : 'ResourceDeleted', message: 'The referenced resource no longer exists'};
|
||||
}
|
||||
switch (resource) {
|
||||
case 'custom_inventory_script':
|
||||
url += 'inventory_scripts/' + obj.id + '/';
|
||||
break;
|
||||
case 'group':
|
||||
if (activity.operation === 'create' || activity.operation === 'delete'){
|
||||
// the API formats the changes.inventory field as str 'myInventoryName-PrimaryKey'
|
||||
var inventory_id = _.last(activity.changes.inventory.split('-'));
|
||||
url += 'inventories/' + inventory_id + '/manage?group=' + activity.changes.id;
|
||||
}
|
||||
else {
|
||||
url += 'inventories/' + activity.summary_fields.inventory[0].id + '/manage?group=' + (activity.changes.id || activity.changes.object1_pk);
|
||||
}
|
||||
break;
|
||||
case 'host':
|
||||
url += 'home/hosts/' + obj.id;
|
||||
break;
|
||||
case 'job':
|
||||
url += 'jobs/' + obj.id;
|
||||
break;
|
||||
case 'inventory':
|
||||
url += 'inventories/' + obj.id + '/';
|
||||
break;
|
||||
case 'schedule':
|
||||
// schedule urls depend on the resource they're associated with
|
||||
if (activity.summary_fields.job_template){
|
||||
url += 'job_templates/' + activity.summary_fields.job_template.id + '/schedules/' + obj.id;
|
||||
}
|
||||
else if (activity.summary_fields.project){
|
||||
url += 'projects/' + activity.summary_fields.project.id + '/schedules/' + obj.id;
|
||||
}
|
||||
else if (activity.summary_fields.system_job_template){
|
||||
url += 'management_jobs/' + activity.summary_fields.system_job_template.id + '/schedules/edit/' + obj.id;
|
||||
}
|
||||
// urls for inventory sync schedules currently depend on having an inventory id and group id
|
||||
else {
|
||||
throw {name : 'NotImplementedError', message : 'activity.summary_fields to build this url not implemented yet'};
|
||||
}
|
||||
break;
|
||||
case 'notification_template':
|
||||
url += `notification_templates/${obj.id}`;
|
||||
break;
|
||||
case 'role':
|
||||
throw {name : 'NotImplementedError', message : 'role object management is not consolidated to a single UI view'};
|
||||
case 'job_template':
|
||||
url += `templates/job_template/${obj.id}`;
|
||||
break;
|
||||
case 'workflow_job_template':
|
||||
url += `templates/workflow_job_template/${obj.id}`;
|
||||
break;
|
||||
default:
|
||||
url += resource + 's/' + obj.id + '/';
|
||||
}
|
||||
return ' <a href=\"' + url + '\"> ' + $filter('sanitize')(obj.name || obj.username) + ' </a> ';
|
||||
}
|
||||
catch(err){
|
||||
$log.debug(err);
|
||||
return ' ' + $filter('sanitize')(obj.name || obj.username || '') + ' ';
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.factory('BuildDescription', ['BuildAnchor', '$log', 'i18n',
|
||||
function (BuildAnchor, $log, i18n) {
|
||||
return function (activity) {
|
||||
|
||||
var pastTense = function(operation){
|
||||
return (/e$/.test(activity.operation)) ? operation + 'd ' : operation + 'ed ';
|
||||
};
|
||||
// convenience method to see if dis+association operation involves 2 groups
|
||||
// the group cases are slightly different because groups can be dis+associated into each other
|
||||
var isGroupRelationship = function(activity){
|
||||
return activity.object1 === 'group' && activity.object2 === 'group' && activity.summary_fields.group.length > 1;
|
||||
};
|
||||
|
||||
// Activity stream objects will outlive the resources they reference
|
||||
// in that case, summary_fields will not be available - show generic error text instead
|
||||
try {
|
||||
activity.description = pastTense(activity.operation);
|
||||
switch(activity.object_association){
|
||||
// explicit role dis+associations
|
||||
case 'role':
|
||||
// object1 field is resource targeted by the dis+association
|
||||
// object2 field is the resource the role is inherited from
|
||||
// summary_field.role[0] contains ref info about the role
|
||||
switch(activity.operation){
|
||||
// expected outcome: "disassociated <object2> role_name from <object1>"
|
||||
case 'disassociate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' from ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
// expected outcome: "associated <object2> role_name to <object1>"
|
||||
case 'associate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' to ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// inherited role dis+associations (logic identical to case 'role')
|
||||
case 'parents':
|
||||
// object1 field is resource targeted by the dis+association
|
||||
// object2 field is the resource the role is inherited from
|
||||
// summary_field.role[0] contains ref info about the role
|
||||
switch(activity.operation){
|
||||
// expected outcome: "disassociated <object2> role_name from <object1>"
|
||||
case 'disassociate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) +
|
||||
'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
// expected outcome: "associated <object2> role_name to <object1>"
|
||||
case 'associate':
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) +
|
||||
'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity);
|
||||
}
|
||||
else{
|
||||
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
|
||||
' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// CRUD operations / resource on resource dis+associations
|
||||
default:
|
||||
switch(activity.operation){
|
||||
// expected outcome: "disassociated <object2> from <object1>"
|
||||
case 'disassociate' :
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) +
|
||||
'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
|
||||
}
|
||||
else {
|
||||
activity.description += activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) +
|
||||
'from ' + activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
}
|
||||
break;
|
||||
// expected outcome "associated <object2> to <object1>"
|
||||
case 'associate':
|
||||
// groups are the only resource that can be associated/disassociated into each other
|
||||
if (isGroupRelationship(activity)){
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) +
|
||||
'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity);
|
||||
}
|
||||
else {
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity) +
|
||||
'to ' + activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity);
|
||||
break;
|
||||
// expected outcome: "operation <object1>"
|
||||
case 'update':
|
||||
activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
|
||||
break;
|
||||
case 'create':
|
||||
activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(err){
|
||||
$log.debug(err);
|
||||
activity.description = i18n._('Event summary not available');
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.factory('ShowDetail', ['$filter', '$rootScope', 'Rest', 'Alert', 'GenerateForm', 'ProcessErrors', 'GetBasePath', 'FormatDate',
|
||||
'ActivityDetailForm', 'Empty', 'Find',
|
||||
function ($filter, $rootScope, Rest, Alert, GenerateForm, ProcessErrors, GetBasePath, FormatDate, ActivityDetailForm, Empty, Find) {
|
||||
return function (params, scope) {
|
||||
|
||||
var activity_id = params.activity_id,
|
||||
activity = Find({ list: params.scope.activities, key: 'id', val: activity_id }),
|
||||
element;
|
||||
|
||||
if (activity) {
|
||||
|
||||
// Grab our element out of the dom
|
||||
element = angular.element(document.getElementById('stream-detail-modal'));
|
||||
|
||||
// Grab the modal's scope so that we can set a few variables
|
||||
scope = element.scope();
|
||||
|
||||
scope.changes = activity.changes;
|
||||
scope.user = ((activity.summary_fields.actor) ? activity.summary_fields.actor.username : 'system') +
|
||||
' on ' + $filter('longDate')(activity.timestamp);
|
||||
scope.operation = activity.description;
|
||||
scope.header = "Event " + activity.id;
|
||||
|
||||
// Open the modal
|
||||
$('#stream-detail-modal').modal({
|
||||
show: true,
|
||||
backdrop: 'static',
|
||||
keyboard: true
|
||||
});
|
||||
|
||||
if (!scope.$$phase) {
|
||||
scope.$digest();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.factory('Stream', ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath',
|
||||
'ProcessErrors', 'Wait', 'StreamList', 'generateList', 'FormatDate', 'BuildDescription',
|
||||
'ShowDetail',
|
||||
function ($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors,
|
||||
Wait, StreamList, GenerateList, FormatDate,
|
||||
BuildDescription, ShowDetail) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope;
|
||||
|
||||
$rootScope.flashMessage = null;
|
||||
|
||||
// descriptive title describing what AS is showing
|
||||
scope.streamTitle = (params && params.title) ? params.title : null;
|
||||
|
||||
scope.refreshStream = function () {
|
||||
$state.go('.', null, {reload: true});
|
||||
};
|
||||
|
||||
scope.showDetail = function (id) {
|
||||
ShowDetail({
|
||||
scope: scope,
|
||||
activity_id: id
|
||||
});
|
||||
};
|
||||
|
||||
if(scope.activities && scope.activities.length > 0) {
|
||||
buildUserAndDescription();
|
||||
}
|
||||
|
||||
scope.$watch('activities', function(){
|
||||
// Watch for future update to scope.activities (like page change, column sort, search, etc)
|
||||
buildUserAndDescription();
|
||||
});
|
||||
|
||||
function buildUserAndDescription(){
|
||||
scope.activities.forEach(function(activity, i) {
|
||||
// build activity.user
|
||||
if (scope.activities[i].summary_fields.actor) {
|
||||
scope.activities[i].user = "<a href=\"/#/users/" + scope.activities[i].summary_fields.actor.id + "\">" +
|
||||
scope.activities[i].summary_fields.actor.username + "</a>";
|
||||
} else {
|
||||
scope.activities[i].user = 'system';
|
||||
}
|
||||
// build description column / action text
|
||||
BuildDescription(scope.activities[i]);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
Loading…
x
Reference in New Issue
Block a user