Track inventory group expand/collapse in local storage. Support multiple inventory syncs running simultaneously. Moved socket connection for job_events to app root. Added on/off socket button so user can tell state of connection and reset/reconnect when needed. Applied AC-1211 fix.

This commit is contained in:
Chris Houseknecht
2014-04-23 18:07:16 -04:00
parent b4d06796a3
commit 49c26aad10
16 changed files with 317 additions and 102 deletions

View File

@@ -409,9 +409,11 @@ angular.module('Tower', [
} }
]) ])
.run(['$cookieStore', '$rootScope', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'ViewLicense', .run(['$cookieStore', '$rootScope', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'ViewLicense',
'Timer', 'ClearScope', 'HideStream', 'Timer', 'ClearScope', 'HideStream', 'Socket',
function ($cookieStore, $rootScope, CheckLicense, $location, Authorization, LoadBasePaths, ViewLicense, function ($cookieStore, $rootScope, CheckLicense, $location, Authorization, LoadBasePaths, ViewLicense,
Timer, ClearScope, HideStream) { Timer, ClearScope, HideStream, Socket) {
var base, sock;
LoadBasePaths(); LoadBasePaths();
@@ -470,7 +472,7 @@ angular.module('Tower', [
} }
// If browser refresh, activate the correct tab // If browser refresh, activate the correct tab
var base = ($location.path().replace(/^\//, '').split('/')[0]); base = ($location.path().replace(/^\//, '').split('/')[0]);
if (base === '') { if (base === '') {
base = 'home'; base = 'home';
$location.path('/home'); $location.path('/home');
@@ -493,5 +495,38 @@ angular.module('Tower', [
e.preventDefault(); e.preventDefault();
$('#' + tabs + ' #' + tab).tab('show'); $('#' + tabs + ' #' + tab).tab('show');
}; };
// Listen for job changes and issue callbacks to initiate
// DOM updates
function openSocket() {
sock = Socket({ scope: $rootScope, endpoint: "jobs" });
sock.init();
setTimeout(function() {
$rootScope.$apply(function() {
sock.checkStatus();
$rootScope.$emit('SocketErrorEncountered');
});
});
sock.on("status_changed", function(data) {
$rootScope.$emit('JobStatusChange', data);
});
}
openSocket();
$rootScope.socketToggle = function() {
switch($rootScope.socketStatus) {
case 'ok':
case 'connecting':
sock = null;
$rootScope.socketStatus = 'error';
$rootScope.socketTip = 'Disconnected. Click to connect.';
break;
case 'error':
sock = null;
$rootScope.socketStatus = '';
$rootScope.socketTip = '';
setTimeout(openSocket, 500);
}
};
} }
]); ]);

View File

@@ -471,11 +471,11 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log
function InventoriesEdit($scope, $location, $routeParams, $compile, $log, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait, function InventoriesEdit($scope, $location, $routeParams, $compile, $log, $rootScope, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait,
GetSyncStatusMsg, InjectHosts, HostsReload, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty, Rest, ProcessErrors, GetSyncStatusMsg, InjectHosts, HostsReload, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty, Rest, ProcessErrors,
InventoryUpdate, Alert, ToggleChildren, ViewUpdateStatus, GroupsCancelUpdate, Find, EditInventoryProperties, HostsEdit, InventoryUpdate, Alert, ToggleChildren, ViewUpdateStatus, GroupsCancelUpdate, Find, EditInventoryProperties, HostsEdit,
HostsDelete, ToggleHostEnabled, CopyMoveGroup, CopyMoveHost, Stream, GetBasePath, ShowJobSummary, ApplyEllipsis, WatchInventoryWindowResize, HostsDelete, ToggleHostEnabled, CopyMoveGroup, CopyMoveHost, Stream, GetBasePath, ShowJobSummary, ApplyEllipsis, WatchInventoryWindowResize,
HelpDialog, InventoryGroupsHelp, Store, ViewJob, Socket) { HelpDialog, InventoryGroupsHelp, Store, ViewJob) {
ClearScope(); ClearScope();
@@ -489,6 +489,40 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, $log, Genera
title: '{{ inventory_name }}' title: '{{ inventory_name }}'
}); });
// Handle inventory sync status changes
if ($rootScope.rmoveJobStatusChange) {
$rootScope.removeJobStatusChange();
}
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function(e, data) {
var group, stat;
if ($scope.groups) {
// Assuming we have a list of groups available
group = Find({ list: $scope.groups, key: 'group_id', val: data.group_id });
if (group) {
// And we found the affected group
$log.debug('Received event for group: ' + group.name);
if (data.status === 'failed' || data.status === 'successful') {
$log.debug('Update completed. Refreshing the tree.');
$scope.refreshGroups(group.id, group.group_id);
}
else {
$log.debug('Status changed to: ' + data.status);
stat = GetSyncStatusMsg({
status: data.status,
has_inventory_sources: group.has_inventory_sources,
source: group.source
});
$log.debug('Changing tooltip to: ' + stat.tooltip);
group.status = data.status;
group.status_class = stat['class'];
group.status_tooltip = stat.tooltip;
group.launch_tooltip = stat.launch_tip;
group.launch_class = stat.launch_class;
}
}
}
});
// After the tree data loads for the first time, generate the groups and hosts lists // After the tree data loads for the first time, generate the groups and hosts lists
if ($scope.removeGroupTreeLoaded) { if ($scope.removeGroupTreeLoaded) {
$scope.removeGroupTreeLoaded(); $scope.removeGroupTreeLoaded();
@@ -643,38 +677,6 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, $log, Genera
} }
}; };
if ($scope.removeWatchUpdateStatus) {
$scope.removeWatchUpdateStatus();
}
$scope.removeWatchUpdateStatus = $scope.$on('WatchUpdateStatus', function(e, job_id, group_id, tree_id) {
var io = Socket({ scope: $scope, endpoint: "jobs" }),
group = Find({ list: $scope.groups, key: 'id', val: tree_id }),
stat;
$log.debug('Watching for updates to job: ' + job_id + ' for group: ' + group_id + ' ' + group.name);
io.init();
io.on("status_changed", function(data) {
Wait('stop');
if (data.status === "failed" || data.status === "successful") {
$log.debug('Update completed. Refreshing the tree.');
$scope.refreshGroups(tree_id, group_id);
}
else {
$log.debug('Status changed to: ' + data.status);
stat = GetSyncStatusMsg({
status: data.status,
has_inventory_sources: group.has_inventory_sources,
source: group.source
});
$log.debug('changing tooltip to: ' + stat.tooltip);
group.status = data.status;
group.status_class = stat['class'];
group.status_tooltip = stat.tooltip;
group.launch_tooltip = stat.launch_tip;
group.launch_class = stat.launch_class;
}
});
});
$scope.createGroup = function () { $scope.createGroup = function () {
GroupsEdit({ GroupsEdit({
scope: $scope, scope: $scope,
@@ -845,10 +847,10 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, $log, Genera
} }
InventoriesEdit.$inject = ['$scope', '$location', '$routeParams', '$compile', '$log', 'GenerateList', 'ClearScope', 'InventoryGroups', 'InventoryHosts', InventoriesEdit.$inject = ['$scope', '$location', '$routeParams', '$compile', '$log', '$rootScope', 'GenerateList', 'ClearScope', 'InventoryGroups', 'InventoryHosts',
'BuildTree', 'Wait', 'GetSyncStatusMsg', 'InjectHosts', 'HostsReload', 'GroupsEdit', 'GroupsDelete', 'Breadcrumbs', 'BuildTree', 'Wait', 'GetSyncStatusMsg', 'InjectHosts', 'HostsReload', 'GroupsEdit', 'GroupsDelete', 'Breadcrumbs',
'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren', 'ViewUpdateStatus', 'GroupsCancelUpdate', 'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren', 'ViewUpdateStatus', 'GroupsCancelUpdate',
'Find', 'EditInventoryProperties', 'HostsEdit', 'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost', 'Find', 'EditInventoryProperties', 'HostsEdit', 'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost',
'Stream', 'GetBasePath', 'ShowJobSummary', 'ApplyEllipsis', 'WatchInventoryWindowResize', 'HelpDialog', 'InventoryGroupsHelp', 'Store', 'Stream', 'GetBasePath', 'ShowJobSummary', 'ApplyEllipsis', 'WatchInventoryWindowResize', 'HelpDialog', 'InventoryGroupsHelp', 'Store',
'ViewJob', 'Socket' 'ViewJob'
]; ];

View File

@@ -12,53 +12,98 @@
'use strict'; 'use strict';
angular.module('ChildrenHelper', ['RestServices', 'Utilities']) angular.module('ChildrenHelper', ['RestServices', 'Utilities'])
.factory('ToggleChildren', [ function () { .factory('ToggleChildren', ['$location', 'Store', function ($location, Store) {
return function (params) { return function (params) {
var scope = params.scope, var scope = params.scope,
list = params.list, list = params.list,
id = params.id, id = params.id,
set = scope[list.name], set = scope[list.name],
i, clicked, found = false; clicked,
//base = $location.path().replace(/^\//, '').split('/')[0],
path = $location.path(),
local_child_store;
function expand(node) { function updateExpand(key, expand) {
var i; var found = false;
set[node].ngicon = 'fa fa-minus-square-o node-toggle'; local_child_store.every(function(child, i) {
for (i = node + 1; i < set.length; i++) { if (child.key === key) {
if (set[i].parent === set[node].id) { local_child_store[i].expand = expand;
set[i].show = true; found = true;
return false;
} }
return true;
});
if (!found) {
local_child_store.push({ key: key, expand: expand });
} }
} }
function updateShow(key, show) {
var found = false;
local_child_store.every(function(child, i) {
if (child.key === key) {
local_child_store[i].show = show;
found = true;
return false;
}
return true;
});
if (!found) {
local_child_store.push({ key: key, show: show });
}
}
function expand(node) {
var i, has_children = false;
for (i = node + 1; i < set.length; i++) {
if (set[i].parent === set[node].id) {
updateShow(set[i].key, true);
set[i].show = true;
}
}
set[node].ngicon = (has_children) ? 'fa fa-minus-square-o node-toggle' : 'fa fa-minus-square-o node-toggle';
}
function collapse(node) { function collapse(node) {
var i; var i, has_children = false;
set[node].ngicon = 'fa fa-plus-square-o node-toggle';
for (i = node + 1; i < set.length; i++) { for (i = node + 1; i < set.length; i++) {
if (set[i].parent === set[node].id) { if (set[i].parent === set[node].id) {
set[i].show = false; set[i].show = false;
has_children = true;
updateShow(set[i].key, false);
if (set[i].related.children) { if (set[i].related.children) {
collapse(i); collapse(i);
} }
} }
} }
set[node].ngicon = (has_children) ? 'fa fa-plus-square-o node-toggle' : 'fa fa-square-o node-toggle';
}
local_child_store = Store(path + '_children');
if (!local_child_store) {
local_child_store = [];
} }
// Scan the array list and find the clicked element // Scan the array list and find the clicked element
for (i = 0; i < set.length && found === false; i++) { set.every(function(row, i) {
if (set[i].id === id) { if (row.id === id) {
clicked = i; clicked = i;
found = true; return false;
} }
} return true;
});
// Expand or collapse children based on clicked element's icon // Expand or collapse children based on clicked element's icon
if (/plus-square-o/.test(set[clicked].ngicon)) { if (/plus-square-o/.test(set[clicked].ngicon)) {
// Expand: lookup and display children // Expand: lookup and display children
expand(clicked); expand(clicked);
updateExpand(set[clicked].key, true);
} else if (/minus-square-o/.test(set[clicked].ngicon)) { } else if (/minus-square-o/.test(set[clicked].ngicon)) {
collapse(clicked); collapse(clicked);
updateExpand(set[clicked].key, false);
} }
Store(path + '_children', local_child_store);
}; };
} }
]); ]);

View File

@@ -1064,6 +1064,8 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
if (!error) { if (!error) {
// Update the parent view with any changes // Update the parent view with any changes
if (groups_reload) { if (groups_reload) {
$log.debug('calling UpdateGroup group_id: ' + group_id + ' name: ' + properties_scope.name + ' description: ' + properties_scope.description +
'has_inventory_sources: ' + ((sources_scope.source && sources_scope.source.value) ? 'true' : 'false') + ' source: ' + sources_scope.source.value );
UpdateGroup({ UpdateGroup({
scope: parent_scope, scope: parent_scope,
group_id: group_id, group_id: group_id,
@@ -1221,8 +1223,9 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
Rest.post(data) Rest.post(data)
.success(function (data) { .success(function (data) {
group_created = true; group_created = true;
group_id = data.id;
sources_scope.source_url = data.related.inventory_source;
if (properties_scope.variables) { if (properties_scope.variables) {
sources_scope.source_url = data.related.inventory_source;
modal_scope.$emit('updateVariables', json_data, data.related.variable_data); modal_scope.$emit('updateVariables', json_data, data.related.variable_data);
} }
else { else {
@@ -1414,7 +1417,7 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
var group; var group;
if (groups && groups.length > 0) { if (groups && groups.length > 0) {
group = groups.pop(); group = groups.pop();
Rest.setUrl(GetBasePath('group') + group.group_id + '/'); Rest.setUrl(GetBasePath('groups') + group.group_id + '/');
Rest.destroy() Rest.destroy()
.success(function() { .success(function() {
scope.$emit('DeleteNextGroup'); scope.$emit('DeleteNextGroup');

View File

@@ -56,6 +56,13 @@ angular.module('InventoryGroupsDefinition', [])
awToolTip: "Refresh the page", awToolTip: "Refresh the page",
ngClick: "refreshGroups()" ngClick: "refreshGroups()"
},*/ },*/
socket: {
mode: 'all',
iconClass: "{{ 'fa fa-power-off fa-lg socket-' + socketStatus }}",
awToolTip: "{{ socketTip }}",
dataTipWatch: "socketTip",
ngClick: "socketToggle()",
},
stream: { stream: {
ngClick: "showGroupActivity()", ngClick: "showGroupActivity()",
awToolTip: "View Activity Stream", awToolTip: "View Activity Stream",

View File

@@ -1203,6 +1203,18 @@ input[type="checkbox"].checkbox-no-label {
} }
/* end */ /* end */
/* Socket icon */
.socket-ok {
color: @green;
}
.socket-error {
color: @red;
}
.socket-connecting {
color: @warning
}
/* end */
.field-success { .field-success {
color: #5bb75b; color: #5bb75b;
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "angular-tz-extensions", "name": "angular-tz-extensions",
"version": "0.3.9", "version": "0.3.10",
"main": "js/angular-timezones.js", "main": "js/angular-timezones.js",
"ignore": [ "ignore": [
".bowerrc", ".bowerrc",
@@ -34,13 +34,14 @@
"Chris Houseknecht" "Chris Houseknecht"
], ],
"license": "MIT", "license": "MIT",
"_release": "0.3.9", "_release": "0.3.10",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "0.3.9", "tag": "0.3.10",
"commit": "dded062e72274e2fc5379bb251c476d152ecc1a1" "commit": "244ac659f0a15fe389f4cb1222ecafdc890e39ef"
}, },
"_source": "git://github.com/chouseknecht/angular-tz-extensions.git", "_source": "git://github.com/chouseknecht/angular-tz-extensions.git",
"_target": "*", "_target": "~0.3.10",
"_originalSource": "angular-tz-extensions" "_originalSource": "angular-tz-extensions",
"_direct": true
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "angular-tz-extensions", "name": "angular-tz-extensions",
"version": "0.3.9", "version": "0.3.10",
"main": "js/angular-timezones.js", "main": "js/angular-timezones.js",
"ignore": [ "ignore": [
".bowerrc", ".bowerrc",

View File

@@ -91,8 +91,8 @@
name: name, name: name,
abbreviation: reference.getTimezoneAbbreviation(), abbreviation: reference.getTimezoneAbbreviation(),
offset: reference.getTimezoneOffset(), offset: reference.getTimezoneOffset(),
region: name.split('/')[0], region: ( (name) ? name.split('/')[0] : '' ),
locality: name.split('/')[1].replace('_', ' ') locality: ( (name) ? name.split('/')[1].replace('_', ' ') : '' )
}; };
return result; return result;
@@ -174,6 +174,7 @@
} }
name = jstz.determine().name(); name = jstz.determine().name();
name = (name === null || name === '' || name === undefined) ? 'America/New_York' : name;
now = new Date(); now = new Date();
return resolve(name, now); return resolve(name, now);
}, },

View File

@@ -1 +1 @@
/*! angular-tz-extensions - v0.0.1 - 2014-03-03 */!function(a){var b=a.angular,c=a.timezoneJS,d=a.jstz,e=function(a){var b,c=new Date;for(b in a)c[b]=a[b];return c},f=b.module("Timezones",[]);f.config(function(){c.fromLocalString=function(a,b){var d,f,g=/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(a),h=[1,4,5,6,7,10,11];for(d=0;f=h[d];++d)g[f]=+g[f]||0;return g[2]=(+g[2]||1)-1,g[3]=+g[3]||1,e(new c.Date(g[1],g[2],g[3],g[4],g[5],g[6],g[7],b))}}),f.constant("$timezones.definitions.location","/tz/data"),f.run(["$timezones.definitions.location",function(a){c.timezone.zoneFileBasePath=a,c.timezone.init({async:!1})}]),f.factory("$timezones",["$log","$http","$timezones.definitions.location",function(a,f,g){var h=function(a,d){var e,f;if("number"==typeof d&&(d=new Date(d)),!b.isDate(d))throw{name:"DateObjectExpected",message:'Expected a Date object; got "'+d+'".'};return d=new c.Date(d,a),e=d.getTimezone(),f={name:e,abbreviation:d.getTimezoneAbbreviation(),offset:d.getTimezoneOffset(),region:e.split("/")[0],locality:e.split("/")[1].replace("_"," ")}};return{align:function(a,d,f){if(!b.isDate(a))throw{name:"DateObjectExpected",message:'Expected a Date object; got "'+a+'".'};try{if(b.isObject(d)&&d.name)return e(new c.Date(a,d.name));if(b.isString(d))return e(new c.Date(a,d))}catch(g){if(!0===f)return a;throw new Error("The timezone argument must either be an Olson name (e.g., America/New_York), or a timezone object (produced by the resolve function) bearing an Olson name on the name property.")}},resolve:h,getLocal:function(){var a,b;if("undefined"==typeof d||"function"!=typeof d.determine)throw{name:"JSTZLibraryMissing",message:"The jsTimezoneDetect library, available at https://bitbucket.org/pellepim/jstimezonedetect, is required to detect the local timezone."};return a=d.determine().name(),b=new Date,h(a,b)},toUTC:function(a,c){var d,e,f=b.isDate(a)?a:new Date(a);return d=f.getUTCFullYear()+"-"+("00"+(f.getUTCMonth()+1)).substr(-2,2)+"-"+("00"+f.getUTCDate()).substr(-2,2)+"T"+("00"+f.getUTCHours()).substr(-2,2)+":"+("00"+f.getUTCMinutes()).substr(-2,2)+":"+("00"+f.getUTCSeconds()).substr(-2,2)+".000Z",e=this.align(new Date(d),c),new Date(e.getTime()+6e4*e.getTimezoneOffset())},getZoneList:function(b){var c,d=[];localStorage.zones?b.$emit("zonesReady"):f({method:"GET",url:g+"/zone.tab"}).success(function(a){var e,f,g=a.match(/[^\r\n]+/g);for(e=0;e<g.length;e++)/^#/.test(g[e])||(f=g[e].split(/\s+/),d.push(f[2]));for(c=d.sort(),d=[],e=0;e<c.length;e++)d.push({name:c[e]});localStorage.zones=JSON.stringify(d),b.$emit("zonesReady")}).error(function(){a.error("Failed to load "+g+"/zone.tab")})}}}]),f.filter("tzAlign",["$timezones",function(a){return function(c,d){if(!(b.isDate(c)||b.isNumber(c)||b.isString(c))||!b.isString(d)&&!b.isObject(d))return c;var e,f,g=c;if(b.isNumber(c)?g=new Date(c):b.isString(c)&&(f=parseInt(c),b.isNumber(f)||(f=Date.parse(c)),g=new Date(f)),!b.isDate(g)||isNaN(g.getTime()))return c;try{e=a.align(g,d,!0)}catch(h){return c}return e&&e.getTime&&!isNaN(e.getTime())?e:c}}])}(this); /*! angular-tz-extensions - v0.3.10 - 2014-04-23 */!function(a){var b=a.angular,c=a.timezoneJS,d=a.jstz,e=function(a){var b,c=new Date;for(b in a)c[b]=a[b];return c},f=b.module("Timezones",[]);f.config(function(){c.fromLocalString=function(a,b){var d,f,g=/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(a),h=[1,4,5,6,7,10,11];for(d=0;f=h[d];++d)g[f]=+g[f]||0;return g[2]=(+g[2]||1)-1,g[3]=+g[3]||1,e(new c.Date(g[1],g[2],g[3],g[4],g[5],g[6],g[7],b))}}),f.constant("$timezones.definitions.location","/tz/data"),f.run(["$timezones.definitions.location",function(a){c.timezone.zoneFileBasePath=a,c.timezone.init({async:!1})}]),f.factory("$timezones",["$log","$http","$timezones.definitions.location",function(a,f,g){var h=function(a,d){var e,f;if("number"==typeof d&&(d=new Date(d)),!b.isDate(d))throw{name:"DateObjectExpected",message:'Expected a Date object; got "'+d+'".'};return d=new c.Date(d,a),e=d.getTimezone(),f={name:e,abbreviation:d.getTimezoneAbbreviation(),offset:d.getTimezoneOffset(),region:e?e.split("/")[0]:"",locality:e?e.split("/")[1].replace("_"," "):""}};return{align:function(a,d,f){if(!b.isDate(a))throw{name:"DateObjectExpected",message:'Expected a Date object; got "'+a+'".'};try{if(b.isObject(d)&&d.name)return e(new c.Date(a,d.name));if(b.isString(d))return e(new c.Date(a,d))}catch(g){if(!0===f)return a;throw new Error("The timezone argument must either be an Olson name (e.g., America/New_York), or a timezone object (produced by the resolve function) bearing an Olson name on the name property.")}},resolve:h,getLocal:function(){var a,b;if("undefined"==typeof d||"function"!=typeof d.determine)throw{name:"JSTZLibraryMissing",message:"The jsTimezoneDetect library, available at https://bitbucket.org/pellepim/jstimezonedetect, is required to detect the local timezone."};return a=d.determine().name(),a=null===a||""===a||void 0===a?"America/New_York":a,b=new Date,h(a,b)},toUTC:function(a,c){var d,e,f=b.isDate(a)?a:new Date(a);return d=f.getUTCFullYear()+"-"+("00"+(f.getUTCMonth()+1)).substr(-2,2)+"-"+("00"+f.getUTCDate()).substr(-2,2)+"T"+("00"+f.getUTCHours()).substr(-2,2)+":"+("00"+f.getUTCMinutes()).substr(-2,2)+":"+("00"+f.getUTCSeconds()).substr(-2,2)+".000Z",e=this.align(new Date(d),c),new Date(e.getTime()+6e4*e.getTimezoneOffset())},getZoneList:function(b){var c,d=[];localStorage.zones?b.$emit("zonesReady"):f({method:"GET",url:g+"/zone.tab"}).success(function(a){var e,f,g=a.match(/[^\r\n]+/g);for(e=0;e<g.length;e++)/^#/.test(g[e])||(f=g[e].split(/\s+/),d.push(f[2]));for(c=d.sort(),d=[],e=0;e<c.length;e++)d.push({name:c[e]});localStorage.zones=JSON.stringify(d),b.$emit("zonesReady")}).error(function(){a.error("Failed to load "+g+"/zone.tab")})}}}]),f.filter("tzAlign",["$timezones",function(a){return function(c,d){if(!(b.isDate(c)||b.isNumber(c)||b.isString(c))||!b.isString(d)&&!b.isObject(d))return c;var e,f,g=c;if(b.isNumber(c)?g=new Date(c):b.isString(c)&&(f=parseInt(c),b.isNumber(f)||(f=Date.parse(c)),g=new Date(f)),!b.isDate(g)||isNaN(g.getTime()))return c;try{e=a.align(g,d,!0)}catch(h){return c}return e&&e.getTime&&!isNaN(e.getTime())?e:c}}])}(this);

View File

@@ -33,8 +33,8 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
} }
]) ])
.factory('BuildTree', ['Rest', 'GetBasePath', 'ProcessErrors', 'SortNodes', 'Wait', 'GetSyncStatusMsg', 'GetHostsStatusMsg', .factory('BuildTree', ['$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'SortNodes', 'Wait', 'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Store',
function (Rest, GetBasePath, ProcessErrors, SortNodes, Wait, GetSyncStatusMsg, GetHostsStatusMsg) { function ($location, Rest, GetBasePath, ProcessErrors, SortNodes, Wait, GetSyncStatusMsg, GetHostsStatusMsg, Store) {
return function (params) { return function (params) {
var inventory_id = params.inventory_id, var inventory_id = params.inventory_id,
@@ -43,7 +43,9 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
emit = params.emit, emit = params.emit,
new_group_id = params.new_group_id, new_group_id = params.new_group_id,
groups = [], groups = [],
id = 1; id = 1,
local_child_store,
path = $location.path();
function buildAllHosts(tree_data) { function buildAllHosts(tree_data) {
// Start our tree object with All Hosts // Start our tree object with All Hosts
@@ -74,10 +76,35 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
groups.push(all_hosts); groups.push(all_hosts);
} }
function getExpandState(key) {
var result = true;
local_child_store.every(function(child) {
if (child.key === key) {
result = child.expand;
return false;
}
return true;
});
return result;
}
function getShowState(key) {
var result = true;
local_child_store.every(function(child) {
if (child.key === key) {
result = (child.show !== undefined) ? child.show : true;
return false;
}
return true;
});
return result;
}
function buildGroups(tree_data, parent, level) { function buildGroups(tree_data, parent, level) {
var children, stat, hosts_status, group, var children, stat, hosts_status, group,
sorted = SortNodes(tree_data); sorted = SortNodes(tree_data),
expand, show;
sorted.forEach( function(row, i) { sorted.forEach( function(row, i) {
id++; id++;
@@ -100,6 +127,9 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
children.push(sorted[i].children[j].id); children.push(sorted[i].children[j].id);
}); });
expand = (sorted[i].children.length > 0) ? getExpandState(sorted[i].id) : false;
show = getShowState(sorted[i].id);
group = { group = {
name: sorted[i].name, name: sorted[i].name,
has_active_failures: sorted[i].has_active_failures, has_active_failures: sorted[i].has_active_failures,
@@ -112,10 +142,11 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
has_inventory_sources: sorted[i].has_inventory_sources, has_inventory_sources: sorted[i].has_inventory_sources,
id: id, id: id,
source: sorted[i].summary_fields.inventory_source.source, source: sorted[i].summary_fields.inventory_source.source,
key: sorted[i].id,
group_id: sorted[i].id, group_id: sorted[i].id,
event_level: level, event_level: level,
children: children, children: children,
ngicon: (sorted[i].children.length > 0) ? 'fa fa-minus-square-o node-toggle' : 'fa fa-square-o node-no-toggle', show: show,
related: sorted[i].related, related: sorted[i].related,
status: sorted[i].summary_fields.inventory_source.status, status: sorted[i].summary_fields.inventory_source.status,
status_class: stat['class'], status_class: stat['class'],
@@ -127,16 +158,37 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
hosts_status_class: hosts_status['class'], hosts_status_class: hosts_status['class'],
inventory_id: inventory_id, inventory_id: inventory_id,
selected_class: '', selected_class: '',
show: true,
isDraggable: true, isDraggable: true,
isDroppable: true isDroppable: true
}; };
groups.push(group); if (sorted[i].children.length > 0) {
if (expand) {
group.ngicon = 'fa fa-minus-square-o node-toggle';
}
else {
group.ngicon = 'fa fa-plus-square-o node-toggle';
}
}
else {
group.ngicon = 'fa fa-square-o node-no-toggle';
}
if (new_group_id && group.group_id === new_group_id) { if (new_group_id && group.group_id === new_group_id) {
// For new group // For new group
// Find parent's expand state and set the show property accordingly.
// If parent is not expanded, then child should be hidden.
if (parent > 0) {
scope.groups.every(function(g) {
if (g.id === group.parent) {
group.show = getExpandState(g.key);
return false;
}
return true;
});
}
scope.selected_tree_id = id; scope.selected_tree_id = id;
scope.selected_group_id = group.group_id; scope.selected_group_id = group.group_id;
} }
groups.push(group);
if (sorted[i].children.length > 0) { if (sorted[i].children.length > 0) {
buildGroups(sorted[i].children, id, level + 1); buildGroups(sorted[i].children, id, level + 1);
} }
@@ -185,7 +237,10 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
}); });
}); });
} }
local_child_store = Store(path + '_children');
if (!local_child_store) {
local_child_store = [];
}
loadTreeData(); loadTreeData();
}; };
} }
@@ -222,8 +277,8 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
// Update date sync status links/icons // Update date sync status links/icons
stat = GetSyncStatusMsg({ stat = GetSyncStatusMsg({
status: scope.groups[i].status, status: scope.groups[i].status,
has_inventory_sources: scope.groups[i].has_inventory_sources, has_inventory_sources: properties.has_inventory_sources,
source: scope.groups[i].source source: properties.source
}); });
scope.groups[i].status_class = stat['class']; scope.groups[i].status_class = stat['class'];
scope.groups[i].status_tooltip = stat.tooltip; scope.groups[i].status_tooltip = stat.tooltip;

View File

@@ -29,9 +29,10 @@ angular.module('SocketIO', ['AuthService', 'Utilities'])
$rootScope.sessionTimer.expireSession(); $rootScope.sessionTimer.expireSession();
$location.url('/login'); $location.url('/login');
} }
else { else if (scope.socketStatus === 'error') {
Alert("Socket Error", "Attempt to refresh page failed with: " + reason, "alert-danger"); Alert("Connection Error", "Error encountered while attempting to connect to the websocket server. Confirm the server " +
// should we do something more here? "is up. Use the <i class=\"fa fa-power-off\"></i> button found on the Inventories, Projects and Jobs pages to reconnect.",
"alert-danger");
} }
}); });
@@ -49,53 +50,75 @@ angular.module('SocketIO', ['AuthService', 'Utilities'])
self.socket = io.connect(url, { headers: self.socket = io.connect(url, { headers:
{ {
'Authorization': 'Token ' + token, 'Authorization': 'Token ' + token,
"X-Auth-Token": 'Token ' + token 'X-Auth-Token': 'Token ' + token
}, },
'connect timeout': 3000, 'connect timeout': 3000,
'try multiple transports': false, 'try multiple transports': false,
'max reconneciton attemps': 3, 'max reconneciton attemps': 3,
'reconnection limit': 3000, 'reconnection limit': 3000,
}); });
self.socket.on('connection', function() { self.socket.on('connection', function() {
$log.debug('Socket connecting...'); $log.debug('Socket connecting...');
self.scope.socket_status = 'connecting'; self.scope.$apply(function () {
self.scope.socketStatus = 'connecting';
self.scope.socketTip = 'Connecting. Click to cancel.';
});
}); });
self.socket.on('connect', function() { self.socket.on('connect', function() {
$log.debug('Socket connection established'); $log.debug('Socket connection established');
self.scope.socket_status = 'ok'; self.scope.$apply(function () {
self.scope.socketStatus = 'ok';
self.scope.socketTip = 'Connected. Click to close.';
});
}); });
self.socket.on('connect_failed', function(reason) { self.socket.on('connect_failed', function(reason) {
var r = reason || 'connection refused by host'; var r = reason || 'connection refused by host';
$log.error('Socket connection failed: ' + r); $log.error('Socket connection failed: ' + r);
self.scope.socket_status = 'error'; self.scope.$apply(function () {
self.scope.socket_reason = r; self.scope.socketStatus = 'error';
self.scope.$emit('SocketErrorEncountered', 'Connection failed: ' + r); self.scope.socketTip = 'Connection failed. Click to retry.';
self.scope.$emit('SocketErrorEncountered');
});
}); });
self.socket.on('diconnect', function() { self.socket.on('diconnect', function() {
$log.debug('Socket disconnected'); $log.debug('Socket disconnected');
self.scope.socket_status = 'disconnected'; self.scope.$apply(function() {
self.socketStatus = 'error';
self.socketTip = 'Disconnected. Click to connect.';
self.scope.$emit('SocketErrorEncountered');
});
}); });
self.socket.on('error', function(reason) { self.socket.on('error', function(reason) {
var r = reason || 'connection refused by host'; var r = reason || 'connection refused by host';
$log.error('Socket error encountered: ' + r); $log.debug('Socket error: ' + r);
self.scope.socket_status = 'error'; self.scope.$apply(function() {
self.scope.socket_reason = r; self.scope.socketStatus = 'error';
self.scope.$emit('SocketErrorEncountered', r); self.scope.socketTip = 'Connection error encountered. Click to retry.';
self.scope.$emit('SocketErrorEncountered');
});
}); });
self.socket.on('reconnecting', function() { self.socket.on('reconnecting', function() {
$log.debug('Socket attempting reconnect...'); $log.debug('Socket attempting reconnect...');
self.scope.socket_status = 'connecting'; self.scope.$apply(function() {
self.scope.socketStatus = 'connecting';
self.scope.socketTip = 'Connecting. Click to cancel.';
});
}); });
self.socket.on('reconnect', function() { self.socket.on('reconnect', function() {
$log.debug('Socket reconnected'); $log.debug('Socket reconnected');
self.scope.socket_status = 'ok'; self.scope.$apply(function() {
self.scope.socketStatus = 'ok';
self.scope.socketTip = 'Connected. Click to close.';
});
}); });
self.socket.on('reconnect_failed', function(reason) { self.socket.on('reconnect_failed', function(reason) {
$log.error('Socket reconnect failed: ' + reason); $log.error('Socket reconnect failed: ' + reason);
self.scope.socket_status = 'error'; self.scope.$apply(function() {
self.scope.socket_reason = reason; self.scope.socketStatus = 'error';
self.scope.$emit('SocketErrorEncountered', 'Connection failed: ' + reason); self.scope.socketTip = 'Connection failed. Click to retry.';
self.scope.$emit('SocketErrorEncountered');
});
}); });
} }
else { else {
@@ -103,6 +126,26 @@ angular.module('SocketIO', ['AuthService', 'Utilities'])
self.scope.$emit('SocketErrorEncountered', 'Session expired'); self.scope.$emit('SocketErrorEncountered', 'Session expired');
} }
}, },
checkStatus: function() {
// Check connection status
var self = this;
if (self.socket.socket.connected) {
$log.debug('Socket connected');
self.scope.socketStatus = 'ok';
self.scope.socketTip = 'Connected. Click to close.';
}
else if (self.socket.socket.connecting || self.socket.socket.reconnecting) {
$log.debug('Socket connecting...');
self.scope.socketStatus = 'connecting';
self.scope.socketTip = 'Connecting. Click to cancel.';
}
else {
$log.debug('Socket error: connection refused');
self.scope.socketStatus = 'error';
self.scope.socketTip = 'Connection failed. Click to retry';
}
return self.scope.socketStatus;
},
on: function (eventName, callback) { on: function (eventName, callback) {
var self = this; var self = this;
self.socket.on(eventName, function () { self.socket.on(eventName, function () {

View File

@@ -117,6 +117,9 @@ angular.module('GeneratorHelpers', [])
case 'stream': case 'stream':
icon = 'fa-clock-o'; icon = 'fa-clock-o';
break; break;
case 'socket':
icon = 'fa-power-off';
break;
case 'refresh': case 'refresh':
icon = 'fa-refresh'; icon = 'fa-refresh';
break; break;
@@ -191,6 +194,7 @@ angular.module('GeneratorHelpers', [])
html += (btn.ngHide) ? Attr(btn, 'ngHide') : ""; html += (btn.ngHide) ? Attr(btn, 'ngHide') : "";
html += (btn.awToolTip) ? Attr(btn, 'awToolTip') : ""; html += (btn.awToolTip) ? Attr(btn, 'awToolTip') : "";
html += (btn.awToolTip && btn.dataPlacement === undefined) ? "data-placement=\"top\" " : ""; html += (btn.awToolTip && btn.dataPlacement === undefined) ? "data-placement=\"top\" " : "";
html += (btn.dataTipWatch) ? "data-tip-watch=\"" + btn.dataTipWatch + "\" " : "";
html += (btn.awPopOver) ? "aw-pop-over=\"" + html += (btn.awPopOver) ? "aw-pop-over=\"" +
btn.awPopOver.replace(/[\'\"]/g, '&quot;') + "\" " : ""; btn.awPopOver.replace(/[\'\"]/g, '&quot;') + "\" " : "";
html += (btn.dataPlacement) ? Attr(btn, 'dataPlacement') : ""; html += (btn.dataPlacement) ? Attr(btn, 'dataPlacement') : "";
@@ -204,10 +208,15 @@ angular.module('GeneratorHelpers', [])
html += " >"; html += " >";
html += (btn.img) ? "<img src=\"" + $basePath + "img/" + btn.img + "\" style=\"width: 12px; height: 12px;\" >" : ""; html += (btn.img) ? "<img src=\"" + $basePath + "img/" + btn.img + "\" style=\"width: 12px; height: 12px;\" >" : "";
html += SelectIcon({ if (btn.iconClass) {
action: action, html += "<i class=\"" + btn.iconClass + "\"></i>";
size: btn.iconSize }
}); else {
html += SelectIcon({
action: action,
size: btn.iconSize
});
}
html += (btn.label) ? " " + btn.label : ""; html += (btn.label) ? " " + btn.label : "";
html += "</button> "; html += "</button> ";

View File

@@ -395,7 +395,8 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += (fAction.dataTitle) ? Attr(fAction, 'dataTitle') : ""; html += (fAction.dataTitle) ? Attr(fAction, 'dataTitle') : "";
for (itm in fAction) { for (itm in fAction) {
if (itm !== 'ngHref' && itm !== 'href' && itm !== 'label' && itm !== 'icon' && itm !== 'class' && if (itm !== 'ngHref' && itm !== 'href' && itm !== 'label' && itm !== 'icon' && itm !== 'class' &&
itm !== 'iconClass' && itm !== "dataPlacement" && itm !== "awPopOver" && itm !== "dataTitle") { itm !== 'iconClass' && itm !== "dataPlacement" && itm !== "awPopOver" &&
itm !== "dataTitle") {
html += Attr(fAction, itm); html += Attr(fAction, itm);
} }
} }

View File

@@ -97,7 +97,7 @@
</div> </div>
</div> </div>
<div ng-show="groupsCount == 0 && hostsCount == 0"> <div ng-show="groupsCount == 0 && hostsCount == 0">
<div class=\"alert alert-info">Delete group <em>{{ group_name }}</em>?</div> <div class="alert alert-info">Delete group <em>{{ group_name }}</em>?</div>
</div> </div>
</div> </div>

View File

@@ -21,7 +21,8 @@
"less.js": "~1.6.3", "less.js": "~1.6.3",
"select2": "~3.4.5", "select2": "~3.4.5",
"sizzle": "1.10.16", "sizzle": "1.10.16",
"d3js": "*" "d3js": "*",
"angular-tz-extensions": "~0.3.10"
}, },
"resolutions": { "resolutions": {
"angular": "1.2.15-build.2398+sha.4bab3d8" "angular": "1.2.15-build.2398+sha.4bab3d8"