github.com/sym3tri/etcd@v0.2.1-0.20140422215517-a563d82f95d6/mod/dashboard/app/coreos-web/coreos.js (about) 1 'use strict'; 2 3 angular.module('underscore', []).factory('_', function($window) { 4 return $window._; 5 }); 6 7 angular.module('jquery', []).factory('$', function($window) { 8 return $window.$; 9 }); 10 11 angular.module('d3', []).factory('d3', function($window) { 12 return $window.d3; 13 }); 14 15 angular.module('coreos.services', [ 16 'coreos.events', 17 'underscore', 18 'jquery' 19 ]); 20 angular.module('coreos.ui', [ 21 'coreos.events', 22 'underscore', 23 'jquery', 24 'd3', 25 'ui.bootstrap' 26 ]); 27 angular.module('coreos.filters', []); 28 angular.module('coreos.events', []); 29 angular.module('coreos', [ 30 'coreos.events', 31 'coreos.services', 32 'coreos.ui', 33 'coreos.filters', 34 'coreos-templates-html', 35 'coreos-templates-svg', 36 37 // External deps. 38 'ngRoute', 39 'ngResource', 40 'ngAnimate', 41 'ui.bootstrap', 42 'underscore', 43 'jquery', 44 'd3' 45 ]) 46 .config(function($compileProvider) { 47 // Allow irc links. 48 $compileProvider 49 .aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|irc):/); 50 }); 51 52 'use strict'; 53 54 angular.module('coreos.filters') 55 .filter('orderObjectBy', function() { 56 return function(items, field, reverse) { 57 var filtered = []; 58 angular.forEach(items, function(item) { 59 filtered.push(item); 60 }); 61 filtered.sort(function (a, b) { 62 return (a[field] > b[field]); 63 }); 64 if (reverse) { 65 filtered.reverse(); 66 } 67 return filtered; 68 }; 69 }); 70 71 'use strict'; 72 73 angular.module('coreos.filters') 74 .filter('utc', function(_) { 75 76 function convertToUtc(date) { 77 return new Date(date.getUTCFullYear(), 78 date.getUTCMonth(), 79 date.getUTCDate(), 80 date.getUTCHours(), 81 date.getUTCMinutes(), 82 date.getUTCSeconds()); 83 } 84 85 return function(input) { 86 if (_.isNumber(input)) { 87 return convertToUtc(new Date(input)); 88 } 89 if (_.isString(input)) { 90 return convertToUtc(new Date(Date.parse(input))); 91 } 92 if (_.isDate(input)) { 93 return convertToUtc(input); 94 } 95 return ''; 96 }; 97 98 }); 99 100 /** 101 * Broadcast when the window size breakpoints change. 102 * TODO(sym3tri): change implementation to use window.matchMedia instead. 103 */ 104 105 'use strict'; 106 107 angular.module('coreos.services') 108 .factory('breakpointSvc', function(_, $window, $rootScope, CORE_CONST, 109 CORE_EVENT) { 110 111 var previousName; 112 113 function getSize() { 114 var width = $window.innerWidth; 115 return _.find(CORE_CONST.BREAKPOINTS, function(bp) { 116 if (bp.min <= width && bp.max > width) { 117 return true; 118 } 119 }).name; 120 } 121 122 function onResize() { 123 var breakpointName = getSize(); 124 if (breakpointName !== previousName) { 125 $rootScope.$broadcast(CORE_EVENT.BREAKPOINT, breakpointName); 126 previousName = breakpointName; 127 } 128 } 129 130 // Broadcast initial size. 131 $rootScope.$broadcast(CORE_EVENT.BREAKPOINT, getSize()); 132 133 // Watch for resizes. 134 angular.element($window).on('resize', _.debounce(onResize, 20, true)); 135 136 return { 137 getSize: getSize 138 }; 139 140 }); 141 142 'use strict'; 143 144 angular.module('coreos.services').provider('configSvc', function() { 145 var configValues = {}; 146 147 this.config = function(newConfig) { 148 if (newConfig) { 149 configValues = newConfig; 150 } else { 151 return configValues; 152 } 153 }; 154 155 this.$get = function() { 156 return { 157 get: function(key) { 158 if (key) { 159 return configValues[key]; 160 } else { 161 return angular.copy(configValues); 162 } 163 }, 164 165 set: function(key, value) { 166 configValues[key] = value; 167 } 168 }; 169 }; 170 171 }); 172 173 'use strict'; 174 175 angular.module('coreos').constant('CORE_CONST', { 176 177 HIGHLIGHT_CSS_CLASS: 'co-an-highlight', 178 179 BREAKPOINTS: [ 180 { 181 name: 'xs', 182 min: 0, 183 max: 480 184 }, 185 { 186 name: 'sm', 187 min: 480, 188 max: 768 189 }, 190 { 191 name: 'md', 192 min: 768, 193 max: 992 194 }, 195 { 196 name: 'lg', 197 min: 992, 198 max: 1200 199 }, 200 { 201 name: 'xl', 202 min: 1200, 203 max: Infinity 204 } 205 ] 206 207 }); 208 209 /** 210 * @fileoverview 211 * 212 * Service for working with cookies since angular's built-in cookie service 213 * leaves much to be desired. 214 */ 215 216 'use strict'; 217 218 angular.module('coreos.services').factory('cookieSvc', 219 function($window, timeSvc) { 220 221 return { 222 223 /** 224 * Create a new cookie. 225 */ 226 create: function(name, value, daysUtilExpires) { 227 var date, expires; 228 if (daysUtilExpires) { 229 date = new Date(); 230 date.setTime(date.getTime() + 231 (daysUtilExpires * timeSvc.ONE_DAY_IN_MS)); 232 expires = '; expires=' + date.toGMTString(); 233 } 234 else { 235 expires = ''; 236 } 237 $window.document.cookie = name + '=' + value + expires + '; path=/'; 238 }, 239 240 /** 241 * Retrieve a cookie by name. 242 */ 243 get: function(name) { 244 var nameEq, cookieList, i, cookieStr; 245 nameEq = name + '='; 246 cookieList = $window.document.cookie.split(';'); 247 for (i = 0; i < cookieList.length; i++) { 248 cookieStr = cookieList[i]; 249 while (cookieStr.charAt(0) === ' ') { 250 cookieStr = cookieStr.substring(1, cookieStr.length); 251 } 252 if (cookieStr.indexOf(nameEq) === 0) { 253 return cookieStr.substring(nameEq.length, cookieStr.length); 254 } 255 } 256 return null; 257 }, 258 259 /** 260 * Delete a cookie by name. 261 */ 262 remove: function(name) { 263 this.create(name, '', -1); 264 } 265 266 }; 267 268 }); 269 270 /** 271 * @fileoverview 272 * 273 * Simply inject this service to start broadcasting events. 274 * It will feature-detect any available browser visibility api. 275 * If the feature exists it will broadcast an event when browser visibiltiy 276 * changes. 277 */ 278 279 'use strict'; 280 281 angular.module('coreos.services') 282 .factory('documentVisibilitySvc', function($rootScope, $document, _, 283 CORE_EVENT) { 284 285 var document = $document[0], 286 features, 287 detectedFeature; 288 289 function broadcastChangeEvent() { 290 $rootScope.$broadcast(CORE_EVENT.DOC_VISIBILITY_CHANGE, 291 document[detectedFeature.propertyName]); 292 } 293 294 features = { 295 standard: { 296 eventName: 'visibilitychange', 297 propertyName: 'hidden' 298 }, 299 moz: { 300 eventName: 'mozvisibilitychange', 301 propertyName: 'mozHidden' 302 }, 303 ms: { 304 eventName: 'msvisibilitychange', 305 propertyName: 'msHidden' 306 }, 307 webkit: { 308 eventName: 'webkitvisibilitychange', 309 propertyName: 'webkitHidden' 310 } 311 }; 312 313 Object.keys(features).some(function(feature) { 314 if (_.isBoolean(document[features[feature].propertyName])) { 315 detectedFeature = features[feature]; 316 return true; 317 } 318 }); 319 320 if (detectedFeature) { 321 $document.on(detectedFeature.eventName, broadcastChangeEvent); 322 } 323 324 return { 325 326 /** 327 * Is the window currently hidden or not. 328 */ 329 isHidden: function() { 330 if (detectedFeature) { 331 return document[detectedFeature.propertyName]; 332 } 333 } 334 335 }; 336 337 }); 338 339 'use strict'; 340 341 angular.module('coreos.events').constant('CORE_EVENT', { 342 PAGE_NOT_FOUND: 'core.event.page_not_found', 343 BREAKPOINT: 'core.event.breakpoint', 344 RESP_ERROR: 'core.event.resp_error', 345 RESP_MUTATE: 'core.event.resp_mutate', 346 DOC_VISIBILITY_CHANGE: 'core.event.doc_visibility_change', 347 POLL_ERROR: 'core.event.poll_error' 348 }); 349 350 /** 351 * @fileoverview 352 * 353 * Utility service to highlight an element or selection of elements. 354 * NOTE: Expects a [HIGHLIGHT_CSS_CLASS] class to be defined in constants. 355 */ 356 357 'use strict'; 358 359 angular.module('coreos.services') 360 .factory('highlighterSvc', function($timeout, $, CORE_CONST) { 361 362 var pendingTimeout; 363 364 return { 365 366 /** 367 * Highlight an element in the DOM. 368 * 369 * @param {String|Element} elemOrSelector 370 */ 371 highlight: function(elemOrSelector) { 372 var elem; 373 if (!elemOrSelector) { 374 return; 375 } 376 elem = $(elemOrSelector); 377 if (elem.hasClass(CORE_CONST.HIGHLIGHT_CSS_CLASS)) { 378 $timeout.cancel(pendingTimeout); 379 elem.removeClass(CORE_CONST.HIGHLIGHT_CSS_CLASS); 380 } 381 elem.addClass(CORE_CONST.HIGHLIGHT_CSS_CLASS); 382 pendingTimeout = $timeout( 383 elem.removeClass.bind(elem, CORE_CONST.HIGHLIGHT_CSS_CLASS), 5000); 384 } 385 386 }; 387 388 }); 389 390 'use strict'; 391 392 angular.module('coreos.services') 393 .factory('interceptorErrorSvc', function($q, $rootScope, CORE_EVENT) { 394 395 function parseMessage(rejection) { 396 var errorMsg; 397 if (rejection.config.description) { 398 errorMsg = 'Error attempting: ' + rejection.config.description; 399 } else { 400 errorMsg = 'A network error occurred.'; 401 } 402 return errorMsg; 403 } 404 405 return { 406 407 /** 408 * For every failing $http request: broadcast an error event. 409 */ 410 'responseError': function(rejection) { 411 if (!rejection.config.supressNotifications) { 412 $rootScope.$broadcast(CORE_EVENT.RESP_ERROR, 413 rejection, 414 parseMessage(rejection)); 415 } 416 return $q.reject(rejection); 417 } 418 419 }; 420 421 }); 422 423 'use strict'; 424 425 angular.module('coreos.services') 426 .factory('interceptorMutateSvc', function($q, $rootScope, CORE_EVENT) { 427 428 // Remove last path segement of a url. 429 function removeLastPath(url) { 430 var newUrl = url.split('/'); 431 newUrl.pop(); 432 newUrl = newUrl.join('/'); 433 return newUrl; 434 } 435 436 return { 437 438 /** 439 * For every successful mutating $http request broadcast the urls. 440 * Useful for cache invalidation. 441 */ 442 'response': function(response) { 443 var method = response.config.method, 444 url = response.config.url, 445 cacheKeys; 446 447 if (method !== 'GET') { 448 cacheKeys = []; 449 cacheKeys.push(url); 450 if (method !== 'POST') { 451 cacheKeys.push(removeLastPath(url)); 452 } 453 $rootScope.$broadcast(CORE_EVENT.RESP_MUTATE, response); 454 } 455 return response || $q.when(response); 456 } 457 458 }; 459 460 }); 461 462 /** 463 * A general purpose polling service. 464 * 465 * Provide a series of options with callacks and this service will start a 466 * poller for the task. 467 * 468 * On failure it will try up to `maxRetries`, then will be killed and callback 469 * to the `catchMaxFail()` function if provided. 470 * 471 * Optionally pass in a `scope` associated with the poller to automatically 472 * kill the poller when the scope is destroyed. 473 * 474 * Global settings for this provider can be configured in the app `config` 475 * stage. Instance will override defaults if provided ot the `register()` 476 * function. 477 * 478 * EXAMPLE USAGE: 479 * 480 * poller.register('myPoller', { 481 * fn: functionToRunRepeadedly, 482 * then: successCallback, 483 * catch: errorCallback, 484 * catchMaxFail: afterMaxFailuresCallback, 485 * scope: $scope, 486 * startIn: 0, 487 * interval: 5000 488 * }); 489 */ 490 491 492 'use strict'; 493 494 angular.module('coreos.services').provider('pollerSvc', function() { 495 var settings = {}, 496 pollers = {}; 497 498 /** 499 * Update global settings for the provider. 500 * @param {Object} newSettings 501 */ 502 this.settings = function(newSettings) { 503 if (newSettings) { 504 settings = newSettings; 505 } else { 506 return settings; 507 } 508 }; 509 510 /** 511 * The main factory method. 512 * Dependencies are injected and is invoked by angular. 513 */ 514 this.$get = function pollerFactory($q, $http, $timeout, _, CORE_EVENT) { 515 /* jshint unused:false */ 516 517 function isRegistered(name) { 518 return !!pollers[name]; 519 } 520 521 /** 522 * Schedule the `execute` function to run. 523 * @param {Number} delay When to start in ms. 524 */ 525 function schedule(name, executor, delay) { 526 var poller = pollers[name]; 527 if (!poller || poller._errorCount > poller.maxRetries) { 528 return; 529 } 530 poller._state = 'waiting'; 531 poller._timeoutPromise = $timeout(executor, delay); 532 } 533 534 /** 535 * Wrap a function to prevent it from running if the current state 536 * is "terminated". 537 */ 538 function runIfActive(name, fn) { 539 var poller = pollers[name]; 540 if (!poller) { 541 return angular.noop; 542 } 543 return function() { 544 if (poller._state !== 'terminated') { 545 return fn.apply(null, arguments); 546 } 547 }; 548 } 549 550 function killPoller(name) { 551 var poller; 552 if (!isRegistered(name)) { 553 return; 554 } 555 poller = pollers[name]; 556 poller._state = 'terminated'; 557 // Cancel the interval timer. 558 if (poller._timeoutPromise) { 559 $timeout.cancel(poller._timeoutPromise); 560 } 561 // Remove the scope.$destroy handler. 562 poller._unlistenDestroy(); 563 // Delete from the list. 564 delete pollers[name]; 565 } 566 567 /** 568 * Create an executor function for a poller with the given name. 569 */ 570 function createExecutor(name) { 571 var poller = pollers[name]; 572 if (!poller) { 573 return angular.noop; 574 } 575 576 /** 577 * The main function that will be run on an interval for a poller. 578 * This wraps the user-provided function, executes callbacks after 579 * completion, and handles scheduling. 580 */ 581 return function execute() { 582 if (poller._paused) { 583 schedule(name, poller._executor, poller.interval); 584 return; 585 } 586 poller._state = 'executing'; 587 poller.fn() 588 .then(runIfActive(name, function() { 589 poller._state = 'success'; 590 poller._errorCount = 0; 591 poller.then.apply(null, arguments); 592 })) 593 .catch(runIfActive(name, function() { 594 var args; 595 poller._state = 'error'; 596 poller._errorCount += 1; 597 poller.catch.apply(null, arguments); 598 if (poller._errorCount > poller.maxRetries) { 599 args = _.toArray(arguments); 600 args.unshift(name); 601 poller.catchMaxFail.apply(null, args); 602 killPoller(name); 603 } 604 })) 605 .finally(runIfActive(name, function() { 606 poller.finally.apply(null, arguments); 607 schedule(name, poller._executor, poller.interval); 608 })); 609 }; 610 } 611 612 return { 613 614 /** 615 * Determines if a poller is already registered by name. 616 * @param {String} name 617 * @return {Boolean} 618 */ 619 isRegistered: isRegistered, 620 621 /** 622 * Register the promise in the index, and schedule it to start polling. 623 * 624 * @param {String} name The uniqe name to associate with the poller. 625 * @param {Object} options 626 */ 627 register: function(name, options) { 628 // kill the old poller if one by same name already exists. 629 if (isRegistered(name)) { 630 this.kill(name); 631 } 632 633 // Initialize all poller options. 634 _.defaults(options, settings, { 635 startIn: 0, 636 maxRetries: 0, 637 catch: angular.noop, 638 then: angular.noop, 639 finally: angular.noop, 640 catchMaxFail: function() { 641 if (options.scope) { 642 options.scope.$emit(CORE_EVENT.POLL_ERROR); 643 } 644 }, 645 _unlistenDestroy: angular.noop, 646 _errorCount: 0, 647 _state: 'starting' 648 }); 649 650 if (options.scope) { 651 // If a scope is provided, automatically kill the poller when the 652 // scope is destroyed. 653 options._unlistenDestroy = 654 options.scope.$on('$destroy', this.kill.bind(this, name)); 655 656 // When scope is prvided automatically pause polling when tab 657 // loses visability. 658 // TODO: add pauseAll() function and move this to app.run() 659 options.scope.$on(CORE_EVENT.DOC_VISIBILITY_CHANGE, 660 function(e, isHidden) { 661 if (isHidden) { 662 options._paused = true; 663 } else { 664 options._paused = false; 665 } 666 }); 667 } 668 669 // Keep track of the poller in the index. 670 pollers[name] = options; 671 672 // Generate the executor wrapper for the poller. 673 options._executor = createExecutor(name); 674 675 // Schedule the initial run of the poller. 676 schedule(name, options._executor, options.startIn); 677 }, 678 679 /** 680 * Kill a poller by name and remove all references, callbacks, etc. 681 * @param {String} name 682 */ 683 kill: function(name) { 684 killPoller(name); 685 }, 686 687 /** 688 * Kill all registered pollers. 689 */ 690 killAll: function() { 691 Object.keys(pollers).forEach(this.kill.bind(this)); 692 } 693 694 }; 695 696 }; 697 698 }); 699 700 /** 701 * @fileoverview 702 * 703 * Utility service that scrolls elements into view. 704 */ 705 706 'use strict'; 707 708 angular.module('coreos.services') 709 .factory('scrollerSvc', function($timeout, $) { 710 711 function scroll(elem) { 712 elem.first()[0].scrollIntoView(); 713 } 714 715 var scrollerSvc = { 716 717 /** 718 * Scroll to the element on the page with matching id. 719 * Adds and removes highlight classes too. 720 * 721 * @param {String|Element} elemOrSelector 722 */ 723 scrollTo: function(elemOrSelector) { 724 var maxTries = 100, 725 numTries = 0, 726 interval = 10, 727 elem; 728 729 if (!elemOrSelector) { 730 return; 731 } 732 733 // Wait for element to appear in DOM if it doesn't exist yet, 734 // then scroll to it. 735 function attemptScroll() { 736 elem = $(elemOrSelector); 737 if (numTries < maxTries) { 738 if (!elem.length) { 739 numTries++; 740 $timeout(attemptScroll, interval); 741 } else { 742 scroll(elem); 743 } 744 } 745 } 746 747 $timeout(attemptScroll, 0); 748 } 749 750 }; 751 752 return scrollerSvc; 753 754 }); 755 756 'use strict'; 757 758 angular.module('coreos.services') 759 .factory('arraySvc', function() { 760 761 return { 762 763 /** 764 * Remove first occurance of an item from an array in-place. 765 * 766 * @param {Arrray} ary Array to mutate. 767 * @param {*} item Array item to remove. 768 * @return {Array} The input array. 769 */ 770 remove: function(ary, item) { 771 var index; 772 if (!ary || !ary.length) { 773 return []; 774 } 775 index = ary.indexOf(item); 776 if (index > -1) { 777 ary.splice(index, 1); 778 } 779 return ary; 780 } 781 782 }; 783 784 }); 785 786 'use strict'; 787 788 angular.module('coreos.services') 789 .factory('mathSvc', function(_) { 790 791 return { 792 793 /** 794 * If passed an array sums all items in the array. 795 * Otherwise sums all arguments together. 796 * 797 * @param {Array|Number...} 798 * @return {Number} 799 */ 800 sum: function() { 801 var ary; 802 if (_.isArray(arguments[0])) { 803 ary = arguments[0]; 804 } else { 805 ary = _.toArray(arguments); 806 } 807 return ary.reduce(function(prev, curr) { 808 return prev + curr; 809 }, 0); 810 } 811 812 }; 813 814 }); 815 816 'use strict'; 817 818 angular.module('coreos.services') 819 .factory('timeSvc', function(_) { 820 821 var ONE_MINUTE_IN_MS = 60 * 1000, 822 ONE_HOUR_IN_MS = ONE_MINUTE_IN_MS * 60, 823 ONE_DAY_IN_MS = ONE_HOUR_IN_MS * 24, 824 ONE_WEEK_IN_MS = ONE_DAY_IN_MS * 7, 825 THIRTY_DAYS_IN_MS = ONE_DAY_IN_MS * 30; 826 827 function getTimestamp(val) { 828 if (val && _.isNumber(val)) { 829 return val; 830 } 831 return Date.now(); 832 } 833 834 return { 835 ONE_MINUTE_IN_MS: ONE_MINUTE_IN_MS, 836 ONE_HOUR_IN_MS: ONE_HOUR_IN_MS, 837 ONE_DAY_IN_MS: ONE_DAY_IN_MS, 838 ONE_WEEK_IN_MS: ONE_WEEK_IN_MS, 839 THIRTY_DAYS_IN_MS: THIRTY_DAYS_IN_MS, 840 841 milliToSecs: function(ms) { 842 return Math.floor(ms / 1000); 843 }, 844 845 secsToMins: function(secs) { 846 return Math.floor(parseInt(secs, 10) / 60) || 0; 847 }, 848 849 minsToSecs: function(mins) { 850 return Math.abs(parseInt(mins, 10) * 60) || 0; 851 }, 852 853 oneHourAgo: function(ts) { 854 return getTimestamp(ts) - this.ONE_HOUR_IN_MS; 855 }, 856 857 oneDayAgo: function(ts) { 858 return getTimestamp(ts) - this.ONE_DAY_IN_MS; 859 }, 860 861 oneWeekAgo: function(ts) { 862 return getTimestamp(ts) - this.ONE_WEEK_IN_MS; 863 }, 864 865 thirtyDaysAgo: function(ts) { 866 return getTimestamp(ts) - this.THIRTY_DAYS_IN_MS; 867 }, 868 869 getRelativeTimestamp: function(term) { 870 var now = Date.now(); 871 switch(term) { 872 case 'month': 873 return this.thirtyDaysAgo(now); 874 case 'week': 875 return this.oneWeekAgo(now); 876 case 'day': 877 return this.oneDayAgo(now); 878 case 'hour': 879 return this.oneHourAgo(now); 880 } 881 } 882 883 }; 884 885 }); 886 887 /** 888 * @fileoverview 889 * Wrap buttons and automatically enable/disbale and show loading indicator. 890 */ 891 892 'use strict'; 893 894 angular.module('coreos.ui') 895 .directive('coBtnBar', function($, $timeout, $compile) { 896 897 return { 898 templateUrl: '/coreos.ui/btn-bar/btn-bar.html', 899 restrict: 'EA', 900 transclude: true, 901 replace: true, 902 scope: { 903 // A promise that indicates completion of async operation. 904 'completePromise': '=' 905 }, 906 link: function(scope, elem) { 907 var linkButton, 908 loaderDirectiveEl; 909 910 linkButton = $('.btn-link', elem).last(); 911 loaderDirectiveEl = 912 angular.element('<co-inline-loader></co-inline-loader>'); 913 $compile(loaderDirectiveEl)(scope); 914 915 function disableButtons() { 916 elem.append(loaderDirectiveEl); 917 $('button', elem).attr('disabled', 'disabled'); 918 linkButton.addClass('hidden'); 919 } 920 921 function enableButtons() { 922 loaderDirectiveEl.remove(); 923 $('button', elem).removeAttr('disabled'); 924 linkButton.removeClass('hidden'); 925 } 926 927 scope.$watch('completePromise', function(completePromise) { 928 if (completePromise) { 929 // Force async execution so disabling the button won't prevent form 930 // submission. 931 $timeout(disableButtons, 0); 932 completePromise.finally(function() { 933 // Also enable buttons asynchronously in case the request completes 934 // before disableButtons() runs. 935 $timeout(enableButtons, 0); 936 }); 937 } 938 }); 939 } 940 941 }; 942 943 }); 944 945 /** 946 * Simple directive to navigate to a route when the 947 * element is clicked on. 948 */ 949 950 'use strict'; 951 952 angular.module('coreos.ui') 953 .directive('coClickNav', function($location) { 954 955 return { 956 restrict: 'A', 957 link: function(scope, elem, attrs) { 958 function onClickHandler(event) { 959 $location.url(attrs.coClickNav); 960 scope.$apply(); 961 event.preventDefault(); 962 event.stopPropagation(); 963 } 964 elem.on('click', onClickHandler); 965 elem.on('$destroy', function() { 966 elem.off('click', onClickHandler); 967 }); 968 } 969 }; 970 971 }); 972 973 /** 974 * @fileoverview 975 * Display a cog icon and construct dropdown menu. 976 */ 977 978 'use strict'; 979 980 angular.module('coreos.ui') 981 .directive('coCog', function() { 982 983 return { 984 templateUrl: '/coreos.ui/cog/cog.html', 985 restrict: 'E', 986 replace: true, 987 scope: { 988 'apps': '=', 989 'options': '=', 990 'size': '@', 991 'anchor': '@' 992 }, 993 link: function(scope, elem) { 994 scope.clickHandler = function($event, option) { 995 $event.stopPropagation(); 996 $event.preventDefault(); 997 if (option.callback) { 998 option.callback(); 999 } 1000 elem.removeClass('open'); 1001 }; 1002 } 1003 }; 1004 1005 }); 1006 1007 'use strict'; 1008 1009 angular.module('coreos.ui') 1010 .controller('ConfirmModalCtrl', function($scope, $modalInstance, 1011 executeFn, title, message, btnText, errorFormatter) { 1012 1013 $scope.errorFormatter = errorFormatter; 1014 $scope.title = title; 1015 $scope.message = message; 1016 $scope.btnText = btnText || 'Confirm'; 1017 1018 $scope.execute = function() { 1019 $scope.requestPromise = executeFn(null, { 1020 supressNotifications: true 1021 }) 1022 .then($modalInstance.close); 1023 }; 1024 1025 $scope.cancel = function() { 1026 $modalInstance.dismiss('cancel'); 1027 }; 1028 1029 }); 1030 1031 /** 1032 * @fileoverview 1033 * An arc donut chart. 1034 */ 1035 1036 // TDOO(sym3tri): add hover text. 1037 1038 'use strict'; 1039 1040 angular.module('coreos.ui') 1041 .directive('coDonut', function(d3, _) { 1042 1043 return { 1044 1045 templateUrl: '/coreos.ui/donut/donut.html', 1046 transclude: true, 1047 restrict: 'E', 1048 replace: true, 1049 scope: { 1050 // The original source data to graph. 1051 percent: '=', 1052 color: '@' 1053 }, 1054 controller: function($scope) { 1055 var outerRadius, circleWidth; 1056 $scope.width = $scope.height = 80; 1057 outerRadius = $scope.width / 2; 1058 circleWidth = 15; 1059 $scope.arc = d3.svg.arc() 1060 .innerRadius(outerRadius - circleWidth) 1061 .outerRadius(outerRadius) 1062 .startAngle(0); 1063 // Constant to turn percents into radian angles. 1064 $scope.tau = 2 * Math.PI; 1065 }, 1066 link: function(scope, elem) { 1067 scope.isRendered = false; 1068 1069 function render() { 1070 var endAngle = scope.tau, // 100% 1071 textColor = '#333', 1072 bgcolor = '#eee', 1073 color = scope.color || '#000', 1074 fontSize = 18; 1075 1076 // Keep track of added DOM elements. 1077 scope.el = {}; 1078 1079 scope.el.svg = d3.select(elem.find('.co-m-gauge__content')[0]) 1080 .append('svg') 1081 .attr('width', scope.width) 1082 .attr('height', scope.height) 1083 .append('g') 1084 .attr('transform', 1085 'translate(' + 1086 scope.width / 2 + ',' + 1087 scope.height / 2 + ')'); 1088 1089 scope.el.text = scope.el.svg.append('text') 1090 .attr('fill', textColor) 1091 .attr('y', Math.floor(fontSize / 3)) 1092 .attr('font-size', fontSize + 'px') 1093 .attr('text-anchor', 'middle'); 1094 1095 scope.el.arcGroup = scope.el.svg.append('g') 1096 .attr('transform', 'rotate(180)'); 1097 1098 scope.el.background = scope.el.arcGroup.append('path') 1099 .datum({ 1100 endAngle: endAngle 1101 }) 1102 .style('fill', bgcolor) 1103 .attr('d', scope.arc); 1104 1105 scope.el.foreground = scope.el.arcGroup.append('path') 1106 .datum({ 1107 endAngle: scope.tau * (scope.percent || 0) 1108 }) 1109 .style('fill', color) 1110 .style('opacity', 0.8) 1111 .attr('d', scope.arc); 1112 1113 scope.isRendered = true; 1114 } 1115 1116 /** 1117 * Update the value of the donut chart. 1118 */ 1119 function updateValue() { 1120 if (!_.isNumber(scope.percent)) { 1121 scope.el.text.text('?'); 1122 return; 1123 } 1124 scope.el.text.text(Math.round(scope.percent * 100) + '%'); 1125 scope.el.foreground.transition() 1126 .duration(750) 1127 .call(arcTween, scope.percent * scope.tau); 1128 } 1129 1130 /** 1131 * Transition function to animate the arc. 1132 */ 1133 function arcTween(transition, newAngle) { 1134 transition.attrTween('d', function(d) { 1135 var interpolate = d3.interpolate(d.endAngle, newAngle); 1136 return function(t) { 1137 d.endAngle = interpolate(t); 1138 return scope.arc(d); 1139 }; 1140 }); 1141 } 1142 1143 /** 1144 * Cleanup. 1145 */ 1146 elem.on('$destroy', function() { 1147 scope.el.svg.remove(); 1148 }); 1149 1150 render(); 1151 1152 scope.$watch('percent', function() { 1153 if (scope.isRendered) { 1154 updateValue(); 1155 } 1156 }); 1157 } 1158 }; 1159 1160 }); 1161 1162 /** 1163 * @fileoverview 1164 * Displays a message based on a promise. 1165 */ 1166 1167 'use strict'; 1168 angular.module('coreos.ui') 1169 1170 1171 .provider('errorMessageSvc', function() { 1172 1173 var formatters = {}; 1174 1175 this.registerFormatter = function(name, fn) { 1176 formatters[name] = fn; 1177 }; 1178 1179 this.$get = function() { 1180 return { 1181 getFormatter: function(name) { 1182 return formatters[name] || angular.noop; 1183 } 1184 }; 1185 }; 1186 1187 }) 1188 1189 1190 .directive('coErrorMessage', function(errorMessageSvc) { 1191 1192 return { 1193 templateUrl: '/coreos.ui/error-message/error-message.html', 1194 restrict: 'E', 1195 replace: true, 1196 scope: { 1197 promise: '=', 1198 formatter: '@', 1199 customMessage: '@message' 1200 }, 1201 controller: function postLink($scope) { 1202 $scope.show = false; 1203 1204 function handler(resp) { 1205 if ($scope.formatter) { 1206 $scope.message = 1207 errorMessageSvc.getFormatter($scope.formatter)(resp); 1208 } else if ($scope.customMessage) { 1209 $scope.message = $scope.customMessage; 1210 } else { 1211 return; 1212 } 1213 $scope.show = true; 1214 } 1215 1216 $scope.$watch('promise', function(promise) { 1217 $scope.show = false; 1218 if (promise && promise.catch) { 1219 promise.catch(handler); 1220 } 1221 }); 1222 1223 } 1224 }; 1225 1226 }); 1227 1228 /** 1229 * @fileoverview 1230 * Inject favicons into the <head>. 1231 * Only use on <head> tag. 1232 */ 1233 1234 1235 'use strict'; 1236 angular.module('coreos.ui') 1237 1238 .directive('coFavicons', function($compile, $rootScope, configSvc) { 1239 /*jshint maxlen:false */ 1240 1241 return { 1242 restrict: 'A', 1243 replace: true, 1244 link: function postLink(scope, elem) { 1245 var newScope = $rootScope.$new(), 1246 htmlTemplate = 1247 '<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{path}}/apple-touch-icon-144-precomposed.png">' + 1248 '<link rel="apple-touch-icon-precomposed" sizes="114x114" href="{{path}}/apple-touch-icon-114-precomposed.png">' + 1249 '<link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{path}}/apple-touch-icon-72-precomposed.png">' + 1250 '<link rel="apple-touch-icon-precomposed" href="{{path}}/apple-touch-icon-57-precomposed.png">' + 1251 '<link rel="shortcut icon" href="{{path}}/favicon.png">'; 1252 newScope.path = configSvc.get('libPath') + '/img'; 1253 elem.append($compile(htmlTemplate)(newScope)); 1254 } 1255 }; 1256 1257 }); 1258 1259 /* 1260 */ 1261 1262 /** 1263 * @fileoverview 1264 * Standard CoreOS footer. 1265 * 1266 */ 1267 1268 'use strict'; 1269 angular.module('coreos.ui') 1270 1271 .directive('coFooter', function() { 1272 return { 1273 templateUrl: '/coreos.ui/footer/footer.html', 1274 transclude: true, 1275 restrict: 'E', 1276 replace: true 1277 }; 1278 }) 1279 1280 .directive('coFooterLink', function() { 1281 return { 1282 templateUrl: '/coreos.ui/footer/footer-link.html', 1283 transclude: true, 1284 restrict: 'E', 1285 replace: true, 1286 scope: { 1287 href: '@', 1288 iconClass: '@' 1289 } 1290 }; 1291 }) 1292 1293 1294 /** 1295 * Convenience wrapper for doing sticky footers. 1296 */ 1297 .directive('coFooterWrapper', function() { 1298 return { 1299 templateUrl: '/coreos.ui/footer/footer-wrapper.html', 1300 transclude: true, 1301 restrict: 'E', 1302 replace: true 1303 }; 1304 1305 }); 1306 1307 /** 1308 * @fileoverview 1309 * Highlight an item when its bound data changes. 1310 */ 1311 1312 'use strict'; 1313 1314 angular.module('coreos.ui') 1315 .directive('coHighlight', function(highlighterSvc) { 1316 1317 return { 1318 restrict: 'A', 1319 link: function(scope, elem, attrs) { 1320 1321 scope.$watch(attrs.coHighlight, function(newValue, oldValue) { 1322 if (newValue !== oldValue) { 1323 highlighterSvc.highlight(elem); 1324 } 1325 }); 1326 1327 } 1328 }; 1329 1330 }); 1331 1332 /** 1333 * @fileoverview 1334 * 1335 * Inline loading indicator widget. 1336 */ 1337 1338 'use strict'; 1339 angular.module('coreos.ui') 1340 1341 .directive('coInlineLoader', function() { 1342 1343 return { 1344 templateUrl: '/coreos.ui/inline-loader/inline-loader.html', 1345 restrict: 'E', 1346 replace: true 1347 }; 1348 1349 }); 1350 1351 /** 1352 * @fileoverview 1353 * 1354 * Loading indicator that centers itself inside its parent. 1355 */ 1356 1357 1358 'use strict'; 1359 angular.module('coreos.ui') 1360 1361 .directive('coLoader', function() { 1362 return { 1363 templateUrl: '/coreos.ui/loader/loader.html', 1364 restrict: 'E', 1365 replace: true 1366 }; 1367 }); 1368 1369 /** 1370 * @fileoverview 1371 * Display page title with primary action link. 1372 */ 1373 1374 1375 'use strict'; 1376 angular.module('coreos.ui') 1377 1378 .directive('coNavTitle', function() { 1379 return { 1380 templateUrl: '/coreos.ui/nav-title/nav-title.html', 1381 transclude: true, 1382 restrict: 'E', 1383 replace: true, 1384 scope: { 1385 title: '@' 1386 } 1387 }; 1388 }); 1389 1390 /** 1391 * @fileoverview 1392 * Top navbar which inlcudes nav links. 1393 */ 1394 1395 1396 'use strict'; 1397 angular.module('coreos.ui') 1398 1399 .directive('coNavbar', function(configSvc) { 1400 1401 return { 1402 templateUrl: '/coreos.ui/navbar/navbar.html', 1403 transclude: true, 1404 restrict: 'E', 1405 replace: true, 1406 controller: function($scope) { 1407 $scope.config = configSvc.get(); 1408 $scope.isCollapsed = true; 1409 } 1410 }; 1411 1412 }) 1413 1414 1415 /** 1416 * Simple directive to create bootstrap friendly navbar links. 1417 * Will automatically add the 'active' class based on the route. 1418 */ 1419 .directive('coNavbarLink', function($location) { 1420 1421 return { 1422 templateUrl: '/coreos.ui/navbar/navbar-link.html', 1423 transclude: true, 1424 restrict: 'E', 1425 replace: true, 1426 scope: { 1427 // The path to link to. 1428 'href': '@' 1429 }, 1430 link: function(scope) { 1431 scope.isActive = function() { 1432 return $location.path() === scope.href; 1433 }; 1434 } 1435 }; 1436 1437 }) 1438 1439 /** 1440 * Optional dropdown menu to put in the right of the navbar. 1441 */ 1442 .directive('coNavbarDropdown', function() { 1443 1444 return { 1445 templateUrl: '/coreos.ui/navbar/navbar-dropdown.html', 1446 transclude: true, 1447 restrict: 'E', 1448 replace: true, 1449 scope: { 1450 text: '@' 1451 } 1452 }; 1453 1454 }); 1455 1456 1457 /** 1458 * @fileoverview 1459 * Directive to easily inline svg images. 1460 * NOTE: kind of a hack to get ng-include to work properly within a directive 1461 * without wrapping it with an extra DOM element. 1462 */ 1463 1464 'use strict'; 1465 1466 angular.module('coreos.ui') 1467 .directive('coSvg', function($, $rootScope, $compile) { 1468 1469 return { 1470 template: '<div></div>', 1471 restrict: 'E', 1472 replace: true, 1473 scope: { 1474 src: '@', 1475 width: '@', 1476 height: '@' 1477 }, 1478 link: function(scope, elem, attrs) { 1479 var containerEl, html, newScope; 1480 newScope = $rootScope.$new(); 1481 html = '<div class="co-m-svg" '+ 1482 'ng-class="classes" ng-style="style" ng-include="src"></div>'; 1483 newScope.style = {}; 1484 if (scope.width) { 1485 newScope.style.width = scope.width + 'px'; 1486 } 1487 if (scope.height) { 1488 newScope.style.height = scope.height + 'px'; 1489 } 1490 if (attrs.class) { 1491 newScope.classes = attrs.class; 1492 } 1493 scope.$watch('src', function(src) { 1494 if (src) { 1495 newScope.src = src; 1496 containerEl = $compile(html)(newScope); 1497 elem.replaceWith(containerEl); 1498 } 1499 }); 1500 } 1501 }; 1502 1503 }); 1504 1505 'use strict'; 1506 1507 angular.module('coreos.ui') 1508 .directive('coTextCopy', function() { 1509 1510 return { 1511 restrict: 'A', 1512 replace: true, 1513 link: function(scope, elem) { 1514 function onClickHandler(event) { 1515 elem.select(); 1516 event.preventDefault(); 1517 event.stopPropagation(); 1518 } 1519 elem.on('click', onClickHandler); 1520 elem.on('$destroy', function() { 1521 elem.off('click', onClickHandler); 1522 }); 1523 } 1524 }; 1525 1526 }); 1527 1528 /** 1529 * @fileoverview 1530 * 1531 * Keeps the title tag updated. 1532 */ 1533 1534 'use strict'; 1535 angular.module('coreos.ui') 1536 1537 1538 .directive('coTitle', function() { 1539 1540 return { 1541 transclude: false, 1542 restrict: 'A', 1543 scope: { 1544 suffix: '@coTitleSuffix' 1545 }, 1546 controller: function($scope, $rootScope, $route) { 1547 $scope.pageTitle = ''; 1548 $scope.defaultTitle = null; 1549 $rootScope.$on('$routeChangeSuccess', function() { 1550 $scope.pageTitle = $route.current.title || $route.current.$$route.title; 1551 }); 1552 }, 1553 link: function(scope, elem) { 1554 scope.$watch('pageTitle', function(title) { 1555 if (title) { 1556 if (!scope.defaultTitle) { 1557 scope.defaultTitle = elem.text(); 1558 } 1559 elem.text(title + ' ' + scope.suffix); 1560 } else { 1561 if (scope.defaultTitle) { 1562 elem.text(scope.defaultTitle); 1563 } 1564 } 1565 }); 1566 } 1567 }; 1568 1569 }); 1570 1571 /** 1572 * @fileoverview 1573 * Directive to display global error or info messages. 1574 * Enqueue messages through the toastSvc. 1575 */ 1576 1577 1578 'use strict'; 1579 1580 angular.module('coreos.ui') 1581 .directive('coToast', function() { 1582 return { 1583 templateUrl: '/coreos.ui/toast/toast.html', 1584 restrict: 'E', 1585 replace: true, 1586 scope: true, 1587 controller: function($scope, toastSvc) { 1588 $scope.messages = toastSvc.messages; 1589 $scope.dismiss = toastSvc.dismiss; 1590 } 1591 }; 1592 }); 1593 1594 1595 angular.module('coreos.services') 1596 .factory('toastSvc', function($timeout) { 1597 1598 var AUTO_DISMISS_TIME = 5000, 1599 service, 1600 lastTimeoutPromise; 1601 1602 function dequeue() { 1603 if (service.messages.length) { 1604 service.messages.shift(); 1605 } 1606 } 1607 1608 function enqueue(type, text) { 1609 service.messages.push({ 1610 type: type, 1611 text: text 1612 }); 1613 lastTimeoutPromise = $timeout(dequeue, AUTO_DISMISS_TIME); 1614 } 1615 1616 function cancelTimeout() { 1617 if (lastTimeoutPromise) { 1618 $timeout.cancel(lastTimeoutPromise); 1619 } 1620 } 1621 1622 service = { 1623 1624 messages: [], 1625 1626 error: enqueue.bind(null, 'error'), 1627 1628 info: enqueue.bind(null, 'info'), 1629 1630 dismiss: function(index) { 1631 cancelTimeout(); 1632 service.messages.splice(index, 1); 1633 }, 1634 1635 dismissAll: function() { 1636 cancelTimeout(); 1637 service.messages.length = 0; 1638 } 1639 1640 }; 1641 1642 return service; 1643 1644 }); 1645 1646 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']); 1647 1648 angular.module("/coreos.ui/btn-bar/btn-bar.html", []).run(["$templateCache", function($templateCache) { 1649 $templateCache.put("/coreos.ui/btn-bar/btn-bar.html", 1650 "<div class=\"co-m-btn-bar\" ng-transclude>\n" + 1651 "</div>\n" + 1652 ""); 1653 }]); 1654 1655 angular.module("/coreos.ui/cog/cog.html", []).run(["$templateCache", function($templateCache) { 1656 $templateCache.put("/coreos.ui/cog/cog.html", 1657 "<div class=\"co-m-cog\">\n" + 1658 " <span class=\"co-m-cog__icon co-m-cog__icon--size-{{size}} dropdown-toggle fa fa-cog\"></span>\n" + 1659 " <ul class=\"dropdown-menu co-m-cog__dropdown co-m-dropdown--dark co-m-cog__dropdown--anchor-{{anchor}}\">\n" + 1660 " <li ng-repeat=\"option in options | orderBy:'weight'\">\n" + 1661 " <a ng-if=\"option.href\" ng-href=\"{{option.href}}\">{{option.label}}</a>\n" + 1662 " <a ng-if=\"!option.href\" ng-click=\"clickHandler($event, option)\">{{option.label}}</a>\n" + 1663 " </li>\n" + 1664 " </ul>\n" + 1665 "</div>\n" + 1666 ""); 1667 }]); 1668 1669 angular.module("/coreos.ui/confirm-modal/confirm-modal.html", []).run(["$templateCache", function($templateCache) { 1670 $templateCache.put("/coreos.ui/confirm-modal/confirm-modal.html", 1671 "<div>\n" + 1672 " <form ng-submit=\"execute()\" name=\"form\" role=\"form\">\n" + 1673 " <div class=\"modal-header\">\n" + 1674 " <h4 class=\"modal-title\" ng-bind=\"title\"></h4>\n" + 1675 " </div>\n" + 1676 " <div class=\"modal-body\" ng-bind=\"message\"></div>\n" + 1677 " <div class=\"modal-footer\" co-btn-bar complete-promise=\"requestPromise\">\n" + 1678 " <co-error-message formatter=\"{{errorFormatter}}\" promise=\"requestPromise\"></co-error-message>\n" + 1679 " <button type=\"submit\" class=\"btn btn-primary\" ng-bind=\"btnText\"></button>\n" + 1680 " <button type=\"button\" ng-click=\"cancel()\" class=\"btn btn-link\">Cancel</button>\n" + 1681 " </div>\n" + 1682 " </form>\n" + 1683 "</div>\n" + 1684 ""); 1685 }]); 1686 1687 angular.module("/coreos.ui/donut/donut.html", []).run(["$templateCache", function($templateCache) { 1688 $templateCache.put("/coreos.ui/donut/donut.html", 1689 "<div class=\"co-m-donut co-m-gauge\">\n" + 1690 " <div class=\"co-m-gauge__content\"></div>\n" + 1691 " <div class=\"co-m-gauge__label\" ng-transclude></div>\n" + 1692 "</div>\n" + 1693 ""); 1694 }]); 1695 1696 angular.module("/coreos.ui/error-message/error-message.html", []).run(["$templateCache", function($templateCache) { 1697 $templateCache.put("/coreos.ui/error-message/error-message.html", 1698 "<div ng-show=\"show\" class=\"co-m-message co-m-message--error co-an-fade-in-out ng-hide\">{{message}}</div>\n" + 1699 ""); 1700 }]); 1701 1702 angular.module("/coreos.ui/favicons/favicons.html", []).run(["$templateCache", function($templateCache) { 1703 $templateCache.put("/coreos.ui/favicons/favicons.html", 1704 ""); 1705 }]); 1706 1707 angular.module("/coreos.ui/footer/footer-link.html", []).run(["$templateCache", function($templateCache) { 1708 $templateCache.put("/coreos.ui/footer/footer-link.html", 1709 "<a class=\"co-m-footer-link\" href=\"{{href}}\">\n" + 1710 " <span class=\"co-m-footer-link--icon\" ng-if=\"iconClass\" ng-class=\"iconClass\"></span>\n" + 1711 " <span ng-transclude></span>\n" + 1712 "</a>\n" + 1713 ""); 1714 }]); 1715 1716 angular.module("/coreos.ui/footer/footer-wrapper.html", []).run(["$templateCache", function($templateCache) { 1717 $templateCache.put("/coreos.ui/footer/footer-wrapper.html", 1718 "<div id=\"co-l-footer-wrapper\">\n" + 1719 " <div ng-transclude></div>\n" + 1720 " <div id=\"co-l-footer-push\"></div>\n" + 1721 "</div>\n" + 1722 ""); 1723 }]); 1724 1725 angular.module("/coreos.ui/footer/footer.html", []).run(["$templateCache", function($templateCache) { 1726 $templateCache.put("/coreos.ui/footer/footer.html", 1727 "<div id=\"co-l-footer\">\n" + 1728 " <div class=\"container\" ng-transclude></div>\n" + 1729 "</div>\n" + 1730 ""); 1731 }]); 1732 1733 angular.module("/coreos.ui/inline-loader/inline-loader.html", []).run(["$templateCache", function($templateCache) { 1734 $templateCache.put("/coreos.ui/inline-loader/inline-loader.html", 1735 "<div class=\"co-m-inline-loader co-an-fade-in-out\">\n" + 1736 " <div class=\"co-m-inline-loader-dot__one\"></div>\n" + 1737 " <div class=\"co-m-inline-loader-dot__two\"></div>\n" + 1738 " <div class=\"co-m-inline-loader-dot__three\"></div>\n" + 1739 "</div>\n" + 1740 ""); 1741 }]); 1742 1743 angular.module("/coreos.ui/loader/loader.html", []).run(["$templateCache", function($templateCache) { 1744 $templateCache.put("/coreos.ui/loader/loader.html", 1745 "<div class=\"co-m-loader co-an-fade-in-out\">\n" + 1746 " <span class=\"co-m-loader__spinner\"></span>\n" + 1747 "</div>\n" + 1748 ""); 1749 }]); 1750 1751 angular.module("/coreos.ui/nav-title/nav-title.html", []).run(["$templateCache", function($templateCache) { 1752 $templateCache.put("/coreos.ui/nav-title/nav-title.html", 1753 "<div class=\"co-m-nav-title row\">\n" + 1754 " <div ng-transclude class=\"col-lg-3 col-md-3 col-sm-3 col-xs-6\"></div>\n" + 1755 " <div class=\"col-lg-6 col-md-6 col-sm-6 col-xs-12\">\n" + 1756 " <h1 class=\"co-m-page-title co-fx-text-shadow\">{{title}}</h1>\n" + 1757 " </div>\n" + 1758 "</div>\n" + 1759 ""); 1760 }]); 1761 1762 angular.module("/coreos.ui/navbar/navbar-dropdown.html", []).run(["$templateCache", function($templateCache) { 1763 $templateCache.put("/coreos.ui/navbar/navbar-dropdown.html", 1764 "<ul class=\"nav navbar-nav pull-right\">\n" + 1765 " <li class=\"dropdown pull-right\">\n" + 1766 " <a href=\"#\" class=\"dropdown-toggle\">{{text}} <b class=\"caret\"></b></a>\n" + 1767 " <ul ng-transclude class=\"dropdown-menu co-m-dropdown--dark\"></ul>\n" + 1768 " </li>\n" + 1769 "</ul>\n" + 1770 ""); 1771 }]); 1772 1773 angular.module("/coreos.ui/navbar/navbar-link.html", []).run(["$templateCache", function($templateCache) { 1774 $templateCache.put("/coreos.ui/navbar/navbar-link.html", 1775 "<li class=\"co-m-nav-link\" ng-class=\"{'active': isActive()}\">\n" + 1776 " <a ng-href=\"{{href}}\" ng-transclude></a>\n" + 1777 "</li>\n" + 1778 ""); 1779 }]); 1780 1781 angular.module("/coreos.ui/navbar/navbar.html", []).run(["$templateCache", function($templateCache) { 1782 $templateCache.put("/coreos.ui/navbar/navbar.html", 1783 "<div class=\"co-m-navbar co-fx-box-shadow navbar navbar-fixed-top\">\n" + 1784 "\n" + 1785 " <div class=\"navbar-header\">\n" + 1786 " <button ng-click=\"isCollapsed = !isCollapsed\" class=\"navbar-toggle\" type=\"button\">\n" + 1787 " <span class=\"glyphicon glyphicon-align-justify\"></span>\n" + 1788 " </button>\n" + 1789 " <a ng-href=\"{{config.siteBasePath}}\" class=\"navbar-brand\">\n" + 1790 " <co-svg class=\"co-m-navbar__logo\" src=\"/coreos.svg/logo.svg\"></co-svg>\n" + 1791 " </a>\n" + 1792 " </div>\n" + 1793 "\n" + 1794 " <div collapse=\"isCollapsed\" ng-transclude class=\"collapse navbar-collapse\"></div>\n" + 1795 "\n" + 1796 "</div>\n" + 1797 ""); 1798 }]); 1799 1800 angular.module("/coreos.ui/toast/toast.html", []).run(["$templateCache", function($templateCache) { 1801 $templateCache.put("/coreos.ui/toast/toast.html", 1802 "<div class=\"co-m-toast\">\n" + 1803 " <div ng-repeat=\"message in messages\"\n" + 1804 " class=\"co-m-toast__message co-m-message co-m-message--{{message.type}} co-an-fade-in-out co-fx-box-shadow\">\n" + 1805 " {{message.text}}\n" + 1806 " <span ng-click=\"dismiss($index)\" class=\"pull-right glyphicon glyphicon-remove text-right co-m-message__close\"></span>\n" + 1807 " </div>\n" + 1808 "</div>\n" + 1809 ""); 1810 }]); 1811 1812 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']); 1813 1814 angular.module("/coreos.svg/globe-only.svg", []).run(["$templateCache", function($templateCache) { 1815 $templateCache.put("/coreos.svg/globe-only.svg", 1816 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + 1817 "<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\n" + 1818 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" + 1819 "<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n" + 1820 " preserveAspectRatio=\"xMidYMin\" viewBox=\"0 0 222.068 222.068\" enable-background=\"new 0 0 222.068 222.068\"\n" + 1821 " xml:space=\"preserve\">\n" + 1822 "<g>\n" + 1823 " <path fill=\"#54A3DA\" d=\"M110.804,3.163c-59.27,0-107.479,48.212-107.479,107.473c0,59.265,48.209,107.474,107.479,107.474\n" + 1824 " c59.252,0,107.465-48.209,107.465-107.474C218.269,51.375,170.056,3.163,110.804,3.163z\"/>\n" + 1825 " <path fill=\"#F1616E\" d=\"M110.804,13.025c-17.283,0-31.941,27.645-37.235,66.069c-0.169,1.236-0.333,2.487-0.478,3.746\n" + 1826 " c-0.723,6.047-1.213,12.335-1.458,18.808c-0.117,2.962-0.175,5.956-0.175,8.988c0,3.029,0.058,6.029,0.175,8.985\n" + 1827 " c0.245,6.472,0.735,12.764,1.458,18.811c8.104,1.049,16.769,1.761,25.807,2.099c3.907,0.146,7.872,0.233,11.907,0.233\n" + 1828 " c4.023,0,8-0.088,11.895-0.233c9.049-0.338,17.708-1.05,25.819-2.099c0.892-0.114,1.77-0.239,2.659-0.368\n" + 1829 " c33.754-4.74,57.235-15.232,57.235-27.428C208.412,56.724,164.707,13.025,110.804,13.025z\"/>\n" + 1830 " <path fill=\"#FFFFFF\" d=\"M151.177,83.205c-0.979-1.428-2.029-2.796-3.148-4.11c-8.956-10.557-22.297-17.265-37.224-17.265\n" + 1831 " c-4.839,0-9.148,7.407-11.907,18.909c-1.096,4.586-1.947,9.819-2.495,15.498c-0.432,4.551-0.665,9.391-0.665,14.399\n" + 1832 " s0.233,9.849,0.665,14.396c4.554,0.432,9.387,0.664,14.402,0.664c5.009,0,9.842-0.232,14.396-0.664\n" + 1833 " c10.011-0.95,18.653-2.875,24.775-5.411c6.046-2.501,9.624-5.615,9.624-8.985C159.599,100.468,156.494,91.024,151.177,83.205z\"/>\n" + 1834 "</g>\n" + 1835 "</svg>\n" + 1836 ""); 1837 }]); 1838 1839 angular.module("/coreos.svg/icon-add.svg", []).run(["$templateCache", function($templateCache) { 1840 $templateCache.put("/coreos.svg/icon-add.svg", 1841 "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n" + 1842 " preserveAspectRatio=\"xMinYMin\" viewBox=\"0 0 72.556 61\" enable-background=\"new 0 0 72.556 61\" xml:space=\"preserve\">\n" + 1843 " <path d=\"M34.521,8v11.088v23v10.737c0,2.209,1.791,4,4,4c2.209,0,4-1.791,4-4V42.067V19.109V8c0-2.209-1.791-4-4-4\n" + 1844 " C36.312,4,34.521,5.791,34.521,8z\"/>\n" + 1845 " <path d=\"M16.109,34.412h11.088h23h10.737c2.209,0,4-1.791,4-4c0-2.209-1.791-4-4-4H50.175H27.217H16.109c-2.209,0-4,1.791-4,4\n" + 1846 " C12.109,32.621,13.9,34.412,16.109,34.412z\"/>\n" + 1847 "</svg>\n" + 1848 ""); 1849 }]); 1850 1851 angular.module("/coreos.svg/icon-back.svg", []).run(["$templateCache", function($templateCache) { 1852 $templateCache.put("/coreos.svg/icon-back.svg", 1853 "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n" + 1854 " preserveAspectRatio=\"xMinYMin\" viewBox=\"0 0 73.356 61\" enable-background=\"new 0 0 73.356 61\" xml:space=\"preserve\">\n" + 1855 " <path d=\"M5.27,33.226l22.428,22.428c1.562,1.562,4.095,1.562,5.657,0c1.562-1.562,1.562-4.095,0-5.657L17.77,34.413h48.514\n" + 1856 " c2.209,0,4-1.791,4-4s-1.791-4-4-4H17.749l15.604-15.582c1.563-1.561,1.565-4.094,0.004-5.657C32.576,4.391,31.552,4,30.527,4\n" + 1857 " c-1.023,0-2.046,0.39-2.827,1.169L5.272,27.567c-0.751,0.75-1.173,1.768-1.173,2.829C4.098,31.458,4.52,32.476,5.27,33.226z\"/>\n" + 1858 "</svg>\n" + 1859 ""); 1860 }]); 1861 1862 angular.module("/coreos.svg/icon-delete.svg", []).run(["$templateCache", function($templateCache) { 1863 $templateCache.put("/coreos.svg/icon-delete.svg", 1864 "<svg version=\"1.1\" fill=\"#f00\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" + 1865 " x=\"0px\" y=\"0px\" preserveAspectRatio=\"xMinYMin\" viewBox=\"0 0 76.143 61\" enable-background=\"new 0 0 76.143 61\" xml:space=\"preserve\">\n" + 1866 " <path d=\"M49.41,13.505l-6.035,6.035L27.112,35.803l-6.035,6.035c-1.562,1.562-1.562,4.095,0,5.657c1.562,1.562,4.095,1.562,5.657,0\n" + 1867 " l6.05-6.05l16.234-16.234l6.05-6.05c1.562-1.562,1.562-4.095,0-5.657C53.505,11.943,50.972,11.943,49.41,13.505z\"/>\n" + 1868 " <path d=\"M21.077,19.162l6.035,6.035L43.375,41.46l6.035,6.035c1.562,1.562,4.095,1.562,5.657,0c1.562-1.562,1.562-4.095,0-5.657\n" + 1869 " l-6.05-6.05L32.783,19.555l-6.05-6.05c-1.562-1.562-4.095-1.562-5.657,0C19.515,15.067,19.515,17.6,21.077,19.162z\"/>\n" + 1870 "</svg>\n" + 1871 ""); 1872 }]); 1873 1874 angular.module("/coreos.svg/icon-reboot.svg", []).run(["$templateCache", function($templateCache) { 1875 $templateCache.put("/coreos.svg/icon-reboot.svg", 1876 "<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n" + 1877 " preserveAspectRatio=\"xMinYMin\" viewBox=\"0 0 65.947 65.41\" enable-background=\"new 0 0 65.947 65.41\" xml:space=\"preserve\">\n" + 1878 "<g>\n" + 1879 " <path d=\"M22.014,15.949c2.428-1.575,5.211-2.632,8.205-3.03c0,0,1.846-0.106,2.797-0.097C44.113,12.932,53.022,22,52.954,33.088\n" + 1880 " l11.226-1.075C63.884,19.558,56.337,8.875,45.553,4.081c-0.043-0.025-0.07-0.061-0.115-0.08c-3.756-1.645-7.896-2.578-12.25-2.621\n" + 1881 " c-0.014,0-0.025,0.002-0.039,0.002c-0.006,0-0.012-0.002-0.02-0.002c-0.691-0.006-1.371,0.021-2.051,0.066\n" + 1882 " c-0.475,0.026-0.941,0.073-1.414,0.12c-0.072,0.008-0.148,0.011-0.221,0.02v0.006c-5.494,0.601-10.578,2.603-14.848,5.678\n" + 1883 " l-3.068-4.523L7.038,21.636l18.849-2.034L22.014,15.949z\"/>\n" + 1884 " <path d=\"M44.204,48.517c-2.428,1.575-5.211,2.632-8.205,3.03c0,0-1.846,0.106-2.797,0.097c-11.098-0.11-20.007-9.178-19.938-20.266\n" + 1885 " L2.038,32.454c0.296,12.454,7.843,23.138,18.627,27.932c0.043,0.025,0.07,0.06,0.115,0.08c3.756,1.644,7.896,2.578,12.25,2.621\n" + 1886 " c0.014,0,0.025-0.002,0.039-0.002c0.006,0,0.012,0.002,0.02,0.002c0.691,0.006,1.371-0.021,2.051-0.065\n" + 1887 " c0.475-0.026,0.941-0.073,1.414-0.12c0.072-0.008,0.148-0.011,0.221-0.02v-0.006c5.494-0.601,10.578-2.604,14.848-5.678\n" + 1888 " l3.068,4.523L59.18,42.83l-18.849,2.034L44.204,48.517z\"/>\n" + 1889 "</g>\n" + 1890 "</svg>\n" + 1891 ""); 1892 }]); 1893 1894 angular.module("/coreos.svg/icon-right-arrow.svg", []).run(["$templateCache", function($templateCache) { 1895 $templateCache.put("/coreos.svg/icon-right-arrow.svg", 1896 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + 1897 "<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\n" + 1898 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" + 1899 "<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n" + 1900 " width=\"6px\" height=\"10px\" viewBox=\"0 0 6 10\" enable-background=\"new 0 0 6 10\" xml:space=\"preserve\">\n" + 1901 "<g>\n" + 1902 " <polygon fill=\"#333333\" points=\"0,0 0,10 6,5 \"/>\n" + 1903 "</g>\n" + 1904 "</svg>\n" + 1905 ""); 1906 }]); 1907 1908 angular.module("/coreos.svg/logo.svg", []).run(["$templateCache", function($templateCache) { 1909 $templateCache.put("/coreos.svg/logo.svg", 1910 "<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n" + 1911 " preserveAspectRatio=\"xMidYMin\" height=\"30px\" viewBox=\"24.5 40.5 744 224\" enable-background=\"new 24.5 40.5 744 224\" xml:space=\"preserve\">\n" + 1912 " <g>\n" + 1913 " <g>\n" + 1914 " <path fill=\"#53A3DA\" d=\"M136.168,45.527C76.898,45.527,28.689,93.739,28.689,153c0,59.265,48.209,107.474,107.479,107.474\n" + 1915 " c59.252,0,107.465-48.209,107.465-107.474C243.633,93.739,195.42,45.527,136.168,45.527z\"/>\n" + 1916 " <path fill=\"#F1606D\" d=\"M136.168,55.389c-17.283,0-31.941,27.645-37.235,66.069c-0.169,1.236-0.333,2.487-0.478,3.746\n" + 1917 " c-0.723,6.047-1.213,12.335-1.458,18.808c-0.117,2.962-0.175,5.956-0.175,8.988c0,3.029,0.058,6.029,0.175,8.985\n" + 1918 " c0.245,6.472,0.735,12.764,1.458,18.811c8.104,1.049,16.769,1.761,25.807,2.099c3.907,0.146,7.872,0.233,11.907,0.233\n" + 1919 " c4.023,0,8-0.088,11.895-0.233c9.049-0.338,17.708-1.05,25.819-2.099c0.892-0.114,1.77-0.239,2.659-0.368\n" + 1920 " c33.754-4.74,57.235-15.232,57.235-27.428C233.776,99.088,190.071,55.389,136.168,55.389z\"/>\n" + 1921 " <path fill=\"#FFFFFF\" d=\"M176.541,125.569c-0.979-1.428-2.029-2.796-3.148-4.11c-8.956-10.557-22.297-17.265-37.224-17.265\n" + 1922 " c-4.839,0-9.148,7.407-11.907,18.909c-1.096,4.586-1.947,9.819-2.495,15.498c-0.432,4.551-0.665,9.391-0.665,14.399\n" + 1923 " s0.233,9.849,0.665,14.396c4.554,0.432,9.387,0.664,14.402,0.664c5.009,0,9.842-0.232,14.396-0.664\n" + 1924 " c10.011-0.95,18.653-2.875,24.775-5.411c6.046-2.501,9.624-5.615,9.624-8.985C184.963,142.832,181.858,133.388,176.541,125.569z\"\n" + 1925 " />\n" + 1926 " </g>\n" + 1927 " <g>\n" + 1928 " <path fill=\"#231F20\" d=\"M344.891,100.053c12.585,0,22.816,6.138,29.262,13.062l-10.064,11.326\n" + 1929 " c-5.353-5.192-11.175-8.495-19.041-8.495c-16.839,0-28.953,14.16-28.953,37.291c0,23.448,11.169,37.608,28.32,37.608\n" + 1930 " c9.128,0,15.895-3.775,21.717-10.228l10.067,11.169c-8.335,9.598-19.038,14.95-32.099,14.95c-26.119,0-46.731-18.88-46.731-53.025\n" + 1931 " C297.37,120.036,318.454,100.053,344.891,100.053z\"/>\n" + 1932 " <path fill=\"#231F20\" d=\"M416.961,125.701c19.352,0,36.822,14.793,36.822,40.597c0,25.647-17.471,40.439-36.822,40.439\n" + 1933 " c-19.197,0-36.66-14.792-36.66-40.439C380.301,140.494,397.764,125.701,416.961,125.701z M416.961,191.945\n" + 1934 " c11.33,0,18.25-10.228,18.25-25.647c0-15.577-6.92-25.804-18.25-25.804s-18.094,10.227-18.094,25.804\n" + 1935 " C398.867,181.717,405.631,191.945,416.961,191.945z\"/>\n" + 1936 " <path fill=\"#231F20\" d=\"M459.771,127.589h14.943l1.26,13.688h0.629c5.506-10.07,13.691-15.577,21.871-15.577\n" + 1937 " c3.938,0,6.455,0.472,8.811,1.574l-3.148,15.734c-2.67-0.784-4.717-1.257-8.018-1.257c-6.139,0-13.539,4.245-18.256,15.893v47.203\n" + 1938 " h-18.092L459.771,127.589L459.771,127.589z\"/>\n" + 1939 " <path fill=\"#231F20\" d=\"M541.121,125.701c20.928,0,31.941,15.107,31.941,36.667c0,3.458-0.314,6.604-0.787,8.495h-49.09\n" + 1940 " c1.57,14.003,10.379,21.869,22.811,21.869c6.613,0,12.273-2.041,17.941-5.662l6.135,11.326\n" + 1941 " c-7.395,4.878-16.676,8.341-26.432,8.341c-21.404,0-38.08-14.95-38.08-40.439C505.561,141.12,523.023,125.701,541.121,125.701z\n" + 1942 " M557.326,159.376c0-12.277-5.189-19.671-15.732-19.671c-9.125,0-16.996,6.768-18.57,19.671H557.326z\"/>\n" + 1943 " <path fill=\"#F1606D\" d=\"M600.602,152.607c0-32.729,17.785-53.344,42.799-53.344c24.863,0,42.641,20.615,42.641,53.344\n" + 1944 " c0,32.889-17.777,54.13-42.641,54.13C618.387,206.737,600.602,185.496,600.602,152.607z M678.49,152.607\n" + 1945 " c0-28.639-14.158-46.731-35.09-46.731c-21.084,0-35.248,18.093-35.248,46.731c0,28.796,14.164,47.521,35.248,47.521\n" + 1946 " C664.332,200.128,678.49,181.403,678.49,152.607z\"/>\n" + 1947 " <path fill=\"#53A4D9\" d=\"M699.738,186.125c7.557,8.495,18.412,14.003,30.529,14.003c15.732,0,25.807-8.499,25.807-20.767\n" + 1948 " c0-12.904-8.494-17.154-18.723-21.717l-15.736-7.082c-8.969-3.936-20.934-10.385-20.934-25.808\n" + 1949 " c0-14.947,12.904-25.492,30.059-25.492c12.588,0,22.658,5.665,28.949,12.435l-4.244,4.878c-5.982-6.452-14.32-10.7-24.705-10.7\n" + 1950 " c-13.691,0-22.816,7.239-22.816,18.565c0,11.962,10.385,16.521,17.936,19.985l15.738,6.921\n" + 1951 " c11.486,5.195,21.713,11.647,21.713,27.539s-13.061,27.851-33.201,27.851c-15.107,0-26.75-6.451-34.932-15.576L699.738,186.125z\"\n" + 1952 " />\n" + 1953 " </g>\n" + 1954 " </g>\n" + 1955 "</svg>\n" + 1956 ""); 1957 }]);