'use strict';
angular.module('underscore', []).factory('_', function($window) {
return $window._;
});
angular.module('jquery', []).factory('$', function($window) {
return $window.$;
});
angular.module('d3', []).factory('d3', function($window) {
return $window.d3;
});
angular.module('coreos.services', [
'coreos.events',
'underscore',
'jquery'
]);
angular.module('coreos.ui', [
'coreos.events',
'underscore',
'jquery',
'd3',
'ui.bootstrap'
]);
angular.module('coreos.filters', []);
angular.module('coreos.events', []);
angular.module('coreos', [
'coreos.events',
'coreos.services',
'coreos.ui',
'coreos.filters',
'coreos-templates-html',
'coreos-templates-svg',
// External deps.
'ngRoute',
'ngResource',
'ngAnimate',
'ui.bootstrap',
'underscore',
'jquery',
'd3'
])
.config(function($compileProvider) {
// Allow irc links.
$compileProvider
.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|irc):/);
});
'use strict';
angular.module('coreos.filters')
.filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (a[field] > b[field]);
});
if (reverse) {
filtered.reverse();
}
return filtered;
};
});
'use strict';
angular.module('coreos.filters')
.filter('utc', function(_) {
function convertToUtc(date) {
return new Date(date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds());
}
return function(input) {
if (_.isNumber(input)) {
return convertToUtc(new Date(input));
}
if (_.isString(input)) {
return convertToUtc(new Date(Date.parse(input)));
}
if (_.isDate(input)) {
return convertToUtc(input);
}
return '';
};
});
/**
* Broadcast when the window size breakpoints change.
* TODO(sym3tri): change implementation to use window.matchMedia instead.
*/
'use strict';
angular.module('coreos.services')
.factory('breakpointSvc', function(_, $window, $rootScope, CORE_CONST,
CORE_EVENT) {
var previousName;
function getSize() {
var width = $window.innerWidth;
return _.find(CORE_CONST.BREAKPOINTS, function(bp) {
if (bp.min <= width && bp.max > width) {
return true;
}
}).name;
}
function onResize() {
var breakpointName = getSize();
if (breakpointName !== previousName) {
$rootScope.$broadcast(CORE_EVENT.BREAKPOINT, breakpointName);
previousName = breakpointName;
}
}
// Broadcast initial size.
$rootScope.$broadcast(CORE_EVENT.BREAKPOINT, getSize());
// Watch for resizes.
angular.element($window).on('resize', _.debounce(onResize, 20, true));
return {
getSize: getSize
};
});
'use strict';
angular.module('coreos.services').provider('configSvc', function() {
var configValues = {};
this.config = function(newConfig) {
if (newConfig) {
configValues = newConfig;
} else {
return configValues;
}
};
this.$get = function() {
return {
get: function(key) {
if (key) {
return configValues[key];
} else {
return angular.copy(configValues);
}
},
set: function(key, value) {
configValues[key] = value;
}
};
};
});
'use strict';
angular.module('coreos').constant('CORE_CONST', {
HIGHLIGHT_CSS_CLASS: 'co-an-highlight',
BREAKPOINTS: [
{
name: 'xs',
min: 0,
max: 480
},
{
name: 'sm',
min: 480,
max: 768
},
{
name: 'md',
min: 768,
max: 992
},
{
name: 'lg',
min: 992,
max: 1200
},
{
name: 'xl',
min: 1200,
max: Infinity
}
]
});
/**
* @fileoverview
*
* Service for working with cookies since angular's built-in cookie service
* leaves much to be desired.
*/
'use strict';
angular.module('coreos.services').factory('cookieSvc',
function($window, timeSvc) {
return {
/**
* Create a new cookie.
*/
create: function(name, value, daysUtilExpires) {
var date, expires;
if (daysUtilExpires) {
date = new Date();
date.setTime(date.getTime() +
(daysUtilExpires * timeSvc.ONE_DAY_IN_MS));
expires = '; expires=' + date.toGMTString();
}
else {
expires = '';
}
$window.document.cookie = name + '=' + value + expires + '; path=/';
},
/**
* Retrieve a cookie by name.
*/
get: function(name) {
var nameEq, cookieList, i, cookieStr;
nameEq = name + '=';
cookieList = $window.document.cookie.split(';');
for (i = 0; i < cookieList.length; i++) {
cookieStr = cookieList[i];
while (cookieStr.charAt(0) === ' ') {
cookieStr = cookieStr.substring(1, cookieStr.length);
}
if (cookieStr.indexOf(nameEq) === 0) {
return cookieStr.substring(nameEq.length, cookieStr.length);
}
}
return null;
},
/**
* Delete a cookie by name.
*/
remove: function(name) {
this.create(name, '', -1);
}
};
});
/**
* @fileoverview
*
* Simply inject this service to start broadcasting events.
* It will feature-detect any available browser visibility api.
* If the feature exists it will broadcast an event when browser visibiltiy
* changes.
*/
'use strict';
angular.module('coreos.services')
.factory('documentVisibilitySvc', function($rootScope, $document, _,
CORE_EVENT) {
var document = $document[0],
features,
detectedFeature;
function broadcastChangeEvent() {
$rootScope.$broadcast(CORE_EVENT.DOC_VISIBILITY_CHANGE,
document[detectedFeature.propertyName]);
}
features = {
standard: {
eventName: 'visibilitychange',
propertyName: 'hidden'
},
moz: {
eventName: 'mozvisibilitychange',
propertyName: 'mozHidden'
},
ms: {
eventName: 'msvisibilitychange',
propertyName: 'msHidden'
},
webkit: {
eventName: 'webkitvisibilitychange',
propertyName: 'webkitHidden'
}
};
Object.keys(features).some(function(feature) {
if (_.isBoolean(document[features[feature].propertyName])) {
detectedFeature = features[feature];
return true;
}
});
if (detectedFeature) {
$document.on(detectedFeature.eventName, broadcastChangeEvent);
}
return {
/**
* Is the window currently hidden or not.
*/
isHidden: function() {
if (detectedFeature) {
return document[detectedFeature.propertyName];
}
}
};
});
'use strict';
angular.module('coreos.events').constant('CORE_EVENT', {
PAGE_NOT_FOUND: 'core.event.page_not_found',
BREAKPOINT: 'core.event.breakpoint',
RESP_ERROR: 'core.event.resp_error',
RESP_MUTATE: 'core.event.resp_mutate',
DOC_VISIBILITY_CHANGE: 'core.event.doc_visibility_change',
POLL_ERROR: 'core.event.poll_error'
});
/**
* @fileoverview
*
* Utility service to highlight an element or selection of elements.
* NOTE: Expects a [HIGHLIGHT_CSS_CLASS] class to be defined in constants.
*/
'use strict';
angular.module('coreos.services')
.factory('highlighterSvc', function($timeout, $, CORE_CONST) {
var pendingTimeout;
return {
/**
* Highlight an element in the DOM.
*
* @param {String|Element} elemOrSelector
*/
highlight: function(elemOrSelector) {
var elem;
if (!elemOrSelector) {
return;
}
elem = $(elemOrSelector);
if (elem.hasClass(CORE_CONST.HIGHLIGHT_CSS_CLASS)) {
$timeout.cancel(pendingTimeout);
elem.removeClass(CORE_CONST.HIGHLIGHT_CSS_CLASS);
}
elem.addClass(CORE_CONST.HIGHLIGHT_CSS_CLASS);
pendingTimeout = $timeout(
elem.removeClass.bind(elem, CORE_CONST.HIGHLIGHT_CSS_CLASS), 5000);
}
};
});
'use strict';
angular.module('coreos.services')
.factory('interceptorErrorSvc', function($q, $rootScope, CORE_EVENT) {
function parseMessage(rejection) {
var errorMsg;
if (rejection.config.description) {
errorMsg = 'Error attempting: ' + rejection.config.description;
} else {
errorMsg = 'A network error occurred.';
}
return errorMsg;
}
return {
/**
* For every failing $http request: broadcast an error event.
*/
'responseError': function(rejection) {
if (!rejection.config.supressNotifications) {
$rootScope.$broadcast(CORE_EVENT.RESP_ERROR,
rejection,
parseMessage(rejection));
}
return $q.reject(rejection);
}
};
});
'use strict';
angular.module('coreos.services')
.factory('interceptorMutateSvc', function($q, $rootScope, CORE_EVENT) {
// Remove last path segement of a url.
function removeLastPath(url) {
var newUrl = url.split('/');
newUrl.pop();
newUrl = newUrl.join('/');
return newUrl;
}
return {
/**
* For every successful mutating $http request broadcast the urls.
* Useful for cache invalidation.
*/
'response': function(response) {
var method = response.config.method,
url = response.config.url,
cacheKeys;
if (method !== 'GET') {
cacheKeys = [];
cacheKeys.push(url);
if (method !== 'POST') {
cacheKeys.push(removeLastPath(url));
}
$rootScope.$broadcast(CORE_EVENT.RESP_MUTATE, response);
}
return response || $q.when(response);
}
};
});
/**
* A general purpose polling service.
*
* Provide a series of options with callacks and this service will start a
* poller for the task.
*
* On failure it will try up to `maxRetries`, then will be killed and callback
* to the `catchMaxFail()` function if provided.
*
* Optionally pass in a `scope` associated with the poller to automatically
* kill the poller when the scope is destroyed.
*
* Global settings for this provider can be configured in the app `config`
* stage. Instance will override defaults if provided ot the `register()`
* function.
*
* EXAMPLE USAGE:
*
* poller.register('myPoller', {
* fn: functionToRunRepeadedly,
* then: successCallback,
* catch: errorCallback,
* catchMaxFail: afterMaxFailuresCallback,
* scope: $scope,
* startIn: 0,
* interval: 5000
* });
*/
'use strict';
angular.module('coreos.services').provider('pollerSvc', function() {
var settings = {},
pollers = {};
/**
* Update global settings for the provider.
* @param {Object} newSettings
*/
this.settings = function(newSettings) {
if (newSettings) {
settings = newSettings;
} else {
return settings;
}
};
/**
* The main factory method.
* Dependencies are injected and is invoked by angular.
*/
this.$get = function pollerFactory($q, $http, $timeout, _, CORE_EVENT) {
/* jshint unused:false */
function isRegistered(name) {
return !!pollers[name];
}
/**
* Schedule the `execute` function to run.
* @param {Number} delay When to start in ms.
*/
function schedule(name, executor, delay) {
var poller = pollers[name];
if (!poller || poller._errorCount > poller.maxRetries) {
return;
}
poller._state = 'waiting';
poller._timeoutPromise = $timeout(executor, delay);
}
/**
* Wrap a function to prevent it from running if the current state
* is "terminated".
*/
function runIfActive(name, fn) {
var poller = pollers[name];
if (!poller) {
return angular.noop;
}
return function() {
if (poller._state !== 'terminated') {
return fn.apply(null, arguments);
}
};
}
function killPoller(name) {
var poller;
if (!isRegistered(name)) {
return;
}
poller = pollers[name];
poller._state = 'terminated';
// Cancel the interval timer.
if (poller._timeoutPromise) {
$timeout.cancel(poller._timeoutPromise);
}
// Remove the scope.$destroy handler.
poller._unlistenDestroy();
// Delete from the list.
delete pollers[name];
}
/**
* Create an executor function for a poller with the given name.
*/
function createExecutor(name) {
var poller = pollers[name];
if (!poller) {
return angular.noop;
}
/**
* The main function that will be run on an interval for a poller.
* This wraps the user-provided function, executes callbacks after
* completion, and handles scheduling.
*/
return function execute() {
if (poller._paused) {
schedule(name, poller._executor, poller.interval);
return;
}
poller._state = 'executing';
poller.fn()
.then(runIfActive(name, function() {
poller._state = 'success';
poller._errorCount = 0;
poller.then.apply(null, arguments);
}))
.catch(runIfActive(name, function() {
var args;
poller._state = 'error';
poller._errorCount += 1;
poller.catch.apply(null, arguments);
if (poller._errorCount > poller.maxRetries) {
args = _.toArray(arguments);
args.unshift(name);
poller.catchMaxFail.apply(null, args);
killPoller(name);
}
}))
.finally(runIfActive(name, function() {
poller.finally.apply(null, arguments);
schedule(name, poller._executor, poller.interval);
}));
};
}
return {
/**
* Determines if a poller is already registered by name.
* @param {String} name
* @return {Boolean}
*/
isRegistered: isRegistered,
/**
* Register the promise in the index, and schedule it to start polling.
*
* @param {String} name The uniqe name to associate with the poller.
* @param {Object} options
*/
register: function(name, options) {
// kill the old poller if one by same name already exists.
if (isRegistered(name)) {
this.kill(name);
}
// Initialize all poller options.
_.defaults(options, settings, {
startIn: 0,
maxRetries: 0,
catch: angular.noop,
then: angular.noop,
finally: angular.noop,
catchMaxFail: function() {
if (options.scope) {
options.scope.$emit(CORE_EVENT.POLL_ERROR);
}
},
_unlistenDestroy: angular.noop,
_errorCount: 0,
_state: 'starting'
});
if (options.scope) {
// If a scope is provided, automatically kill the poller when the
// scope is destroyed.
options._unlistenDestroy =
options.scope.$on('$destroy', this.kill.bind(this, name));
// When scope is prvided automatically pause polling when tab
// loses visability.
// TODO: add pauseAll() function and move this to app.run()
options.scope.$on(CORE_EVENT.DOC_VISIBILITY_CHANGE,
function(e, isHidden) {
if (isHidden) {
options._paused = true;
} else {
options._paused = false;
}
});
}
// Keep track of the poller in the index.
pollers[name] = options;
// Generate the executor wrapper for the poller.
options._executor = createExecutor(name);
// Schedule the initial run of the poller.
schedule(name, options._executor, options.startIn);
},
/**
* Kill a poller by name and remove all references, callbacks, etc.
* @param {String} name
*/
kill: function(name) {
killPoller(name);
},
/**
* Kill all registered pollers.
*/
killAll: function() {
Object.keys(pollers).forEach(this.kill.bind(this));
}
};
};
});
/**
* @fileoverview
*
* Utility service that scrolls elements into view.
*/
'use strict';
angular.module('coreos.services')
.factory('scrollerSvc', function($timeout, $) {
function scroll(elem) {
elem.first()[0].scrollIntoView();
}
var scrollerSvc = {
/**
* Scroll to the element on the page with matching id.
* Adds and removes highlight classes too.
*
* @param {String|Element} elemOrSelector
*/
scrollTo: function(elemOrSelector) {
var maxTries = 100,
numTries = 0,
interval = 10,
elem;
if (!elemOrSelector) {
return;
}
// Wait for element to appear in DOM if it doesn't exist yet,
// then scroll to it.
function attemptScroll() {
elem = $(elemOrSelector);
if (numTries < maxTries) {
if (!elem.length) {
numTries++;
$timeout(attemptScroll, interval);
} else {
scroll(elem);
}
}
}
$timeout(attemptScroll, 0);
}
};
return scrollerSvc;
});
'use strict';
angular.module('coreos.services')
.factory('arraySvc', function() {
return {
/**
* Remove first occurance of an item from an array in-place.
*
* @param {Arrray} ary Array to mutate.
* @param {*} item Array item to remove.
* @return {Array} The input array.
*/
remove: function(ary, item) {
var index;
if (!ary || !ary.length) {
return [];
}
index = ary.indexOf(item);
if (index > -1) {
ary.splice(index, 1);
}
return ary;
}
};
});
'use strict';
angular.module('coreos.services')
.factory('mathSvc', function(_) {
return {
/**
* If passed an array sums all items in the array.
* Otherwise sums all arguments together.
*
* @param {Array|Number...}
* @return {Number}
*/
sum: function() {
var ary;
if (_.isArray(arguments[0])) {
ary = arguments[0];
} else {
ary = _.toArray(arguments);
}
return ary.reduce(function(prev, curr) {
return prev + curr;
}, 0);
}
};
});
'use strict';
angular.module('coreos.services')
.factory('timeSvc', function(_) {
var ONE_MINUTE_IN_MS = 60 * 1000,
ONE_HOUR_IN_MS = ONE_MINUTE_IN_MS * 60,
ONE_DAY_IN_MS = ONE_HOUR_IN_MS * 24,
ONE_WEEK_IN_MS = ONE_DAY_IN_MS * 7,
THIRTY_DAYS_IN_MS = ONE_DAY_IN_MS * 30;
function getTimestamp(val) {
if (val && _.isNumber(val)) {
return val;
}
return Date.now();
}
return {
ONE_MINUTE_IN_MS: ONE_MINUTE_IN_MS,
ONE_HOUR_IN_MS: ONE_HOUR_IN_MS,
ONE_DAY_IN_MS: ONE_DAY_IN_MS,
ONE_WEEK_IN_MS: ONE_WEEK_IN_MS,
THIRTY_DAYS_IN_MS: THIRTY_DAYS_IN_MS,
milliToSecs: function(ms) {
return Math.floor(ms / 1000);
},
secsToMins: function(secs) {
return Math.floor(parseInt(secs, 10) / 60) || 0;
},
minsToSecs: function(mins) {
return Math.abs(parseInt(mins, 10) * 60) || 0;
},
oneHourAgo: function(ts) {
return getTimestamp(ts) - this.ONE_HOUR_IN_MS;
},
oneDayAgo: function(ts) {
return getTimestamp(ts) - this.ONE_DAY_IN_MS;
},
oneWeekAgo: function(ts) {
return getTimestamp(ts) - this.ONE_WEEK_IN_MS;
},
thirtyDaysAgo: function(ts) {
return getTimestamp(ts) - this.THIRTY_DAYS_IN_MS;
},
getRelativeTimestamp: function(term) {
var now = Date.now();
switch(term) {
case 'month':
return this.thirtyDaysAgo(now);
case 'week':
return this.oneWeekAgo(now);
case 'day':
return this.oneDayAgo(now);
case 'hour':
return this.oneHourAgo(now);
}
}
};
});
/**
* @fileoverview
* Wrap buttons and automatically enable/disbale and show loading indicator.
*/
'use strict';
angular.module('coreos.ui')
.directive('coBtnBar', function($, $timeout, $compile) {
return {
templateUrl: '/coreos.ui/btn-bar/btn-bar.html',
restrict: 'EA',
transclude: true,
replace: true,
scope: {
// A promise that indicates completion of async operation.
'completePromise': '='
},
link: function(scope, elem) {
var linkButton,
loaderDirectiveEl;
linkButton = $('.btn-link', elem).last();
loaderDirectiveEl =
angular.element('