'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(''); $compile(loaderDirectiveEl)(scope); function disableButtons() { elem.append(loaderDirectiveEl); $('button', elem).attr('disabled', 'disabled'); linkButton.addClass('hidden'); } function enableButtons() { loaderDirectiveEl.remove(); $('button', elem).removeAttr('disabled'); linkButton.removeClass('hidden'); } scope.$watch('completePromise', function(completePromise) { if (completePromise) { // Force async execution so disabling the button won't prevent form // submission. $timeout(disableButtons, 0); completePromise.finally(function() { // Also enable buttons asynchronously in case the request completes // before disableButtons() runs. $timeout(enableButtons, 0); }); } }); } }; }); /** * Simple directive to navigate to a route when the * element is clicked on. */ 'use strict'; angular.module('coreos.ui') .directive('coClickNav', function($location) { return { restrict: 'A', link: function(scope, elem, attrs) { function onClickHandler(event) { $location.url(attrs.coClickNav); scope.$apply(); event.preventDefault(); event.stopPropagation(); } elem.on('click', onClickHandler); elem.on('$destroy', function() { elem.off('click', onClickHandler); }); } }; }); /** * @fileoverview * Display a cog icon and construct dropdown menu. */ 'use strict'; angular.module('coreos.ui') .directive('coCog', function() { return { templateUrl: '/coreos.ui/cog/cog.html', restrict: 'E', replace: true, scope: { 'apps': '=', 'options': '=', 'size': '@', 'anchor': '@' }, link: function(scope, elem) { scope.clickHandler = function($event, option) { $event.stopPropagation(); $event.preventDefault(); if (option.callback) { option.callback(); } elem.removeClass('open'); }; } }; }); 'use strict'; angular.module('coreos.ui') .controller('ConfirmModalCtrl', function($scope, $modalInstance, executeFn, title, message, btnText, errorFormatter) { $scope.errorFormatter = errorFormatter; $scope.title = title; $scope.message = message; $scope.btnText = btnText || 'Confirm'; $scope.execute = function() { $scope.requestPromise = executeFn(null, { supressNotifications: true }) .then($modalInstance.close); }; $scope.cancel = function() { $modalInstance.dismiss('cancel'); }; }); /** * @fileoverview * An arc donut chart. */ // TDOO(sym3tri): add hover text. 'use strict'; angular.module('coreos.ui') .directive('coDonut', function(d3, _) { return { templateUrl: '/coreos.ui/donut/donut.html', transclude: true, restrict: 'E', replace: true, scope: { // The original source data to graph. percent: '=', color: '@' }, controller: function($scope) { var outerRadius, circleWidth; $scope.width = $scope.height = 80; outerRadius = $scope.width / 2; circleWidth = 15; $scope.arc = d3.svg.arc() .innerRadius(outerRadius - circleWidth) .outerRadius(outerRadius) .startAngle(0); // Constant to turn percents into radian angles. $scope.tau = 2 * Math.PI; }, link: function(scope, elem) { scope.isRendered = false; function render() { var endAngle = scope.tau, // 100% textColor = '#333', bgcolor = '#eee', color = scope.color || '#000', fontSize = 18; // Keep track of added DOM elements. scope.el = {}; scope.el.svg = d3.select(elem.find('.co-m-gauge__content')[0]) .append('svg') .attr('width', scope.width) .attr('height', scope.height) .append('g') .attr('transform', 'translate(' + scope.width / 2 + ',' + scope.height / 2 + ')'); scope.el.text = scope.el.svg.append('text') .attr('fill', textColor) .attr('y', Math.floor(fontSize / 3)) .attr('font-size', fontSize + 'px') .attr('text-anchor', 'middle'); scope.el.arcGroup = scope.el.svg.append('g') .attr('transform', 'rotate(180)'); scope.el.background = scope.el.arcGroup.append('path') .datum({ endAngle: endAngle }) .style('fill', bgcolor) .attr('d', scope.arc); scope.el.foreground = scope.el.arcGroup.append('path') .datum({ endAngle: scope.tau * (scope.percent || 0) }) .style('fill', color) .style('opacity', 0.8) .attr('d', scope.arc); scope.isRendered = true; } /** * Update the value of the donut chart. */ function updateValue() { if (!_.isNumber(scope.percent)) { scope.el.text.text('?'); return; } scope.el.text.text(Math.round(scope.percent * 100) + '%'); scope.el.foreground.transition() .duration(750) .call(arcTween, scope.percent * scope.tau); } /** * Transition function to animate the arc. */ function arcTween(transition, newAngle) { transition.attrTween('d', function(d) { var interpolate = d3.interpolate(d.endAngle, newAngle); return function(t) { d.endAngle = interpolate(t); return scope.arc(d); }; }); } /** * Cleanup. */ elem.on('$destroy', function() { scope.el.svg.remove(); }); render(); scope.$watch('percent', function() { if (scope.isRendered) { updateValue(); } }); } }; }); /** * @fileoverview * Displays a message based on a promise. */ 'use strict'; angular.module('coreos.ui') .provider('errorMessageSvc', function() { var formatters = {}; this.registerFormatter = function(name, fn) { formatters[name] = fn; }; this.$get = function() { return { getFormatter: function(name) { return formatters[name] || angular.noop; } }; }; }) .directive('coErrorMessage', function(errorMessageSvc) { return { templateUrl: '/coreos.ui/error-message/error-message.html', restrict: 'E', replace: true, scope: { promise: '=', formatter: '@', customMessage: '@message' }, controller: function postLink($scope) { $scope.show = false; function handler(resp) { if ($scope.formatter) { $scope.message = errorMessageSvc.getFormatter($scope.formatter)(resp); } else if ($scope.customMessage) { $scope.message = $scope.customMessage; } else { return; } $scope.show = true; } $scope.$watch('promise', function(promise) { $scope.show = false; if (promise && promise.catch) { promise.catch(handler); } }); } }; }); /** * @fileoverview * Inject favicons into the . * Only use on tag. */ 'use strict'; angular.module('coreos.ui') .directive('coFavicons', function($compile, $rootScope, configSvc) { /*jshint maxlen:false */ return { restrict: 'A', replace: true, link: function postLink(scope, elem) { var newScope = $rootScope.$new(), htmlTemplate = '' + '' + '' + '' + ''; newScope.path = configSvc.get('libPath') + '/img'; elem.append($compile(htmlTemplate)(newScope)); } }; }); /* */ /** * @fileoverview * Standard CoreOS footer. * */ 'use strict'; angular.module('coreos.ui') .directive('coFooter', function() { return { templateUrl: '/coreos.ui/footer/footer.html', transclude: true, restrict: 'E', replace: true }; }) .directive('coFooterLink', function() { return { templateUrl: '/coreos.ui/footer/footer-link.html', transclude: true, restrict: 'E', replace: true, scope: { href: '@', iconClass: '@' } }; }) /** * Convenience wrapper for doing sticky footers. */ .directive('coFooterWrapper', function() { return { templateUrl: '/coreos.ui/footer/footer-wrapper.html', transclude: true, restrict: 'E', replace: true }; }); /** * @fileoverview * Highlight an item when its bound data changes. */ 'use strict'; angular.module('coreos.ui') .directive('coHighlight', function(highlighterSvc) { return { restrict: 'A', link: function(scope, elem, attrs) { scope.$watch(attrs.coHighlight, function(newValue, oldValue) { if (newValue !== oldValue) { highlighterSvc.highlight(elem); } }); } }; }); /** * @fileoverview * * Inline loading indicator widget. */ 'use strict'; angular.module('coreos.ui') .directive('coInlineLoader', function() { return { templateUrl: '/coreos.ui/inline-loader/inline-loader.html', restrict: 'E', replace: true }; }); /** * @fileoverview * * Loading indicator that centers itself inside its parent. */ 'use strict'; angular.module('coreos.ui') .directive('coLoader', function() { return { templateUrl: '/coreos.ui/loader/loader.html', restrict: 'E', replace: true }; }); /** * @fileoverview * Display page title with primary action link. */ 'use strict'; angular.module('coreos.ui') .directive('coNavTitle', function() { return { templateUrl: '/coreos.ui/nav-title/nav-title.html', transclude: true, restrict: 'E', replace: true, scope: { title: '@' } }; }); /** * @fileoverview * Top navbar which inlcudes nav links. */ 'use strict'; angular.module('coreos.ui') .directive('coNavbar', function(configSvc) { return { templateUrl: '/coreos.ui/navbar/navbar.html', transclude: true, restrict: 'E', replace: true, controller: function($scope) { $scope.config = configSvc.get(); $scope.isCollapsed = true; } }; }) /** * Simple directive to create bootstrap friendly navbar links. * Will automatically add the 'active' class based on the route. */ .directive('coNavbarLink', function($location) { return { templateUrl: '/coreos.ui/navbar/navbar-link.html', transclude: true, restrict: 'E', replace: true, scope: { // The path to link to. 'href': '@' }, link: function(scope) { scope.isActive = function() { return $location.path() === scope.href; }; } }; }) /** * Optional dropdown menu to put in the right of the navbar. */ .directive('coNavbarDropdown', function() { return { templateUrl: '/coreos.ui/navbar/navbar-dropdown.html', transclude: true, restrict: 'E', replace: true, scope: { text: '@' } }; }); /** * @fileoverview * Directive to easily inline svg images. * NOTE: kind of a hack to get ng-include to work properly within a directive * without wrapping it with an extra DOM element. */ 'use strict'; angular.module('coreos.ui') .directive('coSvg', function($, $rootScope, $compile) { return { template: '
', restrict: 'E', replace: true, scope: { src: '@', width: '@', height: '@' }, link: function(scope, elem, attrs) { var containerEl, html, newScope; newScope = $rootScope.$new(); html = '
'; newScope.style = {}; if (scope.width) { newScope.style.width = scope.width + 'px'; } if (scope.height) { newScope.style.height = scope.height + 'px'; } if (attrs.class) { newScope.classes = attrs.class; } scope.$watch('src', function(src) { if (src) { newScope.src = src; containerEl = $compile(html)(newScope); elem.replaceWith(containerEl); } }); } }; }); 'use strict'; angular.module('coreos.ui') .directive('coTextCopy', function() { return { restrict: 'A', replace: true, link: function(scope, elem) { function onClickHandler(event) { elem.select(); event.preventDefault(); event.stopPropagation(); } elem.on('click', onClickHandler); elem.on('$destroy', function() { elem.off('click', onClickHandler); }); } }; }); /** * @fileoverview * * Keeps the title tag updated. */ 'use strict'; angular.module('coreos.ui') .directive('coTitle', function() { return { transclude: false, restrict: 'A', scope: { suffix: '@coTitleSuffix' }, controller: function($scope, $rootScope, $route) { $scope.pageTitle = ''; $scope.defaultTitle = null; $rootScope.$on('$routeChangeSuccess', function() { $scope.pageTitle = $route.current.title || $route.current.$$route.title; }); }, link: function(scope, elem) { scope.$watch('pageTitle', function(title) { if (title) { if (!scope.defaultTitle) { scope.defaultTitle = elem.text(); } elem.text(title + ' ' + scope.suffix); } else { if (scope.defaultTitle) { elem.text(scope.defaultTitle); } } }); } }; }); /** * @fileoverview * Directive to display global error or info messages. * Enqueue messages through the toastSvc. */ 'use strict'; angular.module('coreos.ui') .directive('coToast', function() { return { templateUrl: '/coreos.ui/toast/toast.html', restrict: 'E', replace: true, scope: true, controller: function($scope, toastSvc) { $scope.messages = toastSvc.messages; $scope.dismiss = toastSvc.dismiss; } }; }); angular.module('coreos.services') .factory('toastSvc', function($timeout) { var AUTO_DISMISS_TIME = 5000, service, lastTimeoutPromise; function dequeue() { if (service.messages.length) { service.messages.shift(); } } function enqueue(type, text) { service.messages.push({ type: type, text: text }); lastTimeoutPromise = $timeout(dequeue, AUTO_DISMISS_TIME); } function cancelTimeout() { if (lastTimeoutPromise) { $timeout.cancel(lastTimeoutPromise); } } service = { messages: [], error: enqueue.bind(null, 'error'), info: enqueue.bind(null, 'info'), dismiss: function(index) { cancelTimeout(); service.messages.splice(index, 1); }, dismissAll: function() { cancelTimeout(); service.messages.length = 0; } }; return service; }); angular.module('coreos-templates-html', ['/coreos.ui/btn-bar/btn-bar.html', '/coreos.ui/cog/cog.html', '/coreos.ui/confirm-modal/confirm-modal.html', '/coreos.ui/donut/donut.html', '/coreos.ui/error-message/error-message.html', '/coreos.ui/favicons/favicons.html', '/coreos.ui/footer/footer-link.html', '/coreos.ui/footer/footer-wrapper.html', '/coreos.ui/footer/footer.html', '/coreos.ui/inline-loader/inline-loader.html', '/coreos.ui/loader/loader.html', '/coreos.ui/nav-title/nav-title.html', '/coreos.ui/navbar/navbar-dropdown.html', '/coreos.ui/navbar/navbar-link.html', '/coreos.ui/navbar/navbar.html', '/coreos.ui/toast/toast.html']); angular.module("/coreos.ui/btn-bar/btn-bar.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/btn-bar/btn-bar.html", "
\n" + "
\n" + ""); }]); angular.module("/coreos.ui/cog/cog.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/cog/cog.html", "
\n" + " \n" + " \n" + "
\n" + ""); }]); angular.module("/coreos.ui/confirm-modal/confirm-modal.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/confirm-modal/confirm-modal.html", "
\n" + "
\n" + "
\n" + "

\n" + "
\n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + "
\n" + ""); }]); angular.module("/coreos.ui/donut/donut.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/donut/donut.html", "
\n" + "
\n" + "
\n" + "
\n" + ""); }]); angular.module("/coreos.ui/error-message/error-message.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/error-message/error-message.html", "
{{message}}
\n" + ""); }]); angular.module("/coreos.ui/favicons/favicons.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/favicons/favicons.html", ""); }]); angular.module("/coreos.ui/footer/footer-link.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/footer/footer-link.html", "\n" + " \n" + " \n" + "\n" + ""); }]); angular.module("/coreos.ui/footer/footer-wrapper.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/footer/footer-wrapper.html", "\n" + ""); }]); angular.module("/coreos.ui/footer/footer.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/footer/footer.html", "
\n" + "
\n" + "
\n" + ""); }]); angular.module("/coreos.ui/inline-loader/inline-loader.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/inline-loader/inline-loader.html", "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + ""); }]); angular.module("/coreos.ui/loader/loader.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/loader/loader.html", "
\n" + " \n" + "
\n" + ""); }]); angular.module("/coreos.ui/nav-title/nav-title.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/nav-title/nav-title.html", "
\n" + "
\n" + "
\n" + "

{{title}}

\n" + "
\n" + "
\n" + ""); }]); angular.module("/coreos.ui/navbar/navbar-dropdown.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/navbar/navbar-dropdown.html", "\n" + ""); }]); angular.module("/coreos.ui/navbar/navbar-link.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/navbar/navbar-link.html", "
  • \n" + " \n" + "
  • \n" + ""); }]); angular.module("/coreos.ui/navbar/navbar.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/navbar/navbar.html", "
    \n" + "\n" + "
    \n" + " \n" + " \n" + " \n" + " \n" + "
    \n" + "\n" + "
    \n" + "\n" + "
    \n" + ""); }]); angular.module("/coreos.ui/toast/toast.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.ui/toast/toast.html", "
    \n" + "
    \n" + " {{message.text}}\n" + " \n" + "
    \n" + "
    \n" + ""); }]); angular.module('coreos-templates-svg', ['/coreos.svg/globe-only.svg', '/coreos.svg/icon-add.svg', '/coreos.svg/icon-back.svg', '/coreos.svg/icon-delete.svg', '/coreos.svg/icon-reboot.svg', '/coreos.svg/icon-right-arrow.svg', '/coreos.svg/logo.svg']); angular.module("/coreos.svg/globe-only.svg", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.svg/globe-only.svg", "\n" + "\n" + "\n" + "\n" + "\n" + " \n" + " \n" + " \n" + "\n" + "\n" + ""); }]); angular.module("/coreos.svg/icon-add.svg", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.svg/icon-add.svg", "\n" + " \n" + " \n" + "\n" + ""); }]); angular.module("/coreos.svg/icon-back.svg", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.svg/icon-back.svg", "\n" + " \n" + "\n" + ""); }]); angular.module("/coreos.svg/icon-delete.svg", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.svg/icon-delete.svg", "\n" + " \n" + " \n" + "\n" + ""); }]); angular.module("/coreos.svg/icon-reboot.svg", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.svg/icon-reboot.svg", "\n" + "\n" + " \n" + " \n" + "\n" + "\n" + ""); }]); angular.module("/coreos.svg/icon-right-arrow.svg", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.svg/icon-right-arrow.svg", "\n" + "\n" + "\n" + "\n" + "\n" + " \n" + "\n" + "\n" + ""); }]); angular.module("/coreos.svg/logo.svg", []).run(["$templateCache", function($templateCache) { $templateCache.put("/coreos.svg/logo.svg", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + ""); }]);