var componentNamespaces = ["kubernetesApp.components.dashboard"]; // APP START // **************************** // /www/app/assets/app.js is autogenerated. Do not modify. // Changes should be made in /master/modules/js or /master/components//js // **************************** // ----------------------------------- var app = angular.module('kubernetesApp', [ 'ngRoute', 'ngMaterial', 'ngLodash', 'door3.css', 'kubernetesApp.config', 'kubernetesApp.services', 'angular.filter' ].concat(componentNamespaces)); angular.module('kubernetesApp.config', []); angular.module('kubernetesApp.services', ['kubernetesApp.config']); app.config([ '$routeProvider', function($routeProvider) { $routeProvider.when("/404", {templateUrl: "views/partials/404.html"}) // else 404 .otherwise({redirectTo: "/404"}); } ]) .config([ '$routeProvider', 'manifestRoutes', function($routeProvider, manifestRoutes) { angular.forEach(manifestRoutes, function(r) { var route = { templateUrl: r.templateUrl }; if (r.controller) { route.controller = r.controller; } if (r.css) { route.css = r.css; } $routeProvider.when(r.url, route); }); } ]); app.directive('includeReplace', function() { 'use strict'; return { require: 'ngInclude', restrict: 'A', /* optional */ link: function(scope, el, attrs) { el.replaceWith(el.children()); } }; }) .directive('compile', ["$compile", function($compile) { 'use strict'; return function(scope, element, attrs) { scope.$watch(function(scope) { return scope.$eval(attrs.compile); }, function(value) { element.html(value); $compile(element.contents())(scope); }); }; }]) .directive("kubernetesUiMenu", function() { 'use strict'; return { templateUrl: "views/partials/kubernetes-ui-menu.tmpl.html" }; }) .directive('menuToggle', function() { 'use strict'; return { scope: {section: '='}, templateUrl: 'views/partials/menu-toggle.tmpl.html', link: function($scope, $element) { var controller = $element.parent().controller(); $scope.isOpen = function() { return controller.isOpen($scope.section); }; $scope.toggle = function() { controller.toggleOpen($scope.section); }; var parentNode = $element[0].parentNode.parentNode.parentNode; if (parentNode.classList.contains('parent-list-item')) { var heading = parentNode.querySelector('h2'); $element[0].firstChild.setAttribute('aria-describedby', heading.id); } } }; }); app.filter('startFrom', function() { 'use strict'; return function(input, start) { return input.slice(start); }; }) .filter('nospace', function() { 'use strict'; return function(value) { return (!value) ? '' : value.replace(/ /g, ''); }; }); app.run(['$route', angular.noop]) .run(["lodash", function(lodash) { // Alias lodash window['_'] = lodash; }]); app.service('SidebarService', [ '$rootScope', function($rootScope) { var service = this; service.sidebarItems = []; service.clearSidebarItems = function() { service.sidebarItems = []; }; service.renderSidebar = function() { var _entries = ''; service.sidebarItems.forEach(function(entry) { _entries += entry.Html; }); if (_entries) { $rootScope.sidenavLeft = '
' + _entries + '
'; } }; service.addSidebarItem = function(item) { service.sidebarItems.push(item); service.sidebarItems.sort(function(a, b) { return (a.order > b.order) ? 1 : ((b.order > a.order) ? -1 : 0); }); }; } ]); app.value("tabs", [{"component":"dashboard","title":"Dashboard"}]); app.constant("manifestRoutes", [{"description":"Dashboard visualization.","url":"/dashboard/","templateUrl":"components/dashboard/pages/home.html"},{"description":"Pods","url":"/dashboard/pods","templateUrl":"components/dashboard/views/listPods.html"},{"description":"Pod Visualizer","url":"/dashboard/visualpods","templateUrl":"components/dashboard/views/listPodsVisualizer.html"},{"description":"Services","url":"/dashboard/services","templateUrl":"components/dashboard/views/listServices.html"},{"description":"Replication Controllers","url":"/dashboard/replicationcontrollers","templateUrl":"components/dashboard/views/listReplicationControllers.html"},{"description":"Events","url":"/dashboard/events","templateUrl":"components/dashboard/views/listEvents.html"},{"description":"Minions","url":"/dashboard/minions","templateUrl":"components/dashboard/views/listMinions.html"},{"description":"Replication Controller","url":"/dashboard/replicationcontrollers/:replicationControllerId","templateUrl":"components/dashboard/views/replication.html"},{"description":"Service","url":"/dashboard/services/:serviceId","templateUrl":"components/dashboard/views/service.html"},{"description":"Explore","url":"/dashboard/groups/:grouping*?/selector/:selector*?","templateUrl":"components/dashboard/views/groups.html"},{"description":"Pod","url":"/dashboard/pods/:podId","templateUrl":"components/dashboard/views/pod.html"}]); angular.module("kubernetesApp.config", []) .constant("ENV", { "/": { "k8sApiServer": "/api/v1beta2", "k8sDataServer": "/cluster", "k8sDataPollMinIntervalSec": 10, "k8sDataPollMaxIntervalSec": 120, "k8sDataPollErrorThreshold": 5, "cAdvisorProxy": "", "cAdvisorPort": "4194" } }) .constant("ngConstant", true) ; /**========================================================= * Module: config.js * App routes and resources configuration =========================================================*/ /**========================================================= * Module: constants.js * Define constants to inject across the application =========================================================*/ /**========================================================= * Module: main.js * Main Application Controller =========================================================*/ /**========================================================= * Module: tabs-global.js * Page Controller =========================================================*/ app.controller('TabCtrl', [ '$scope', '$location', 'tabs', function($scope, $location, tabs) { $scope.tabs = tabs; $scope.switchTab = function(index) { var location_path = $location.path(); var tab = tabs[index]; if (tab) { var path = '/%s'.format(tab.component); if (location_path.indexOf(path) == -1) { $location.path(path); } } }; } ]); /**========================================================= * Module: sidebar.js * Wraps the sidebar and handles collapsed state =========================================================*/ (function() { "use strict"; angular.module('kubernetesApp.services') .service('cAdvisorService', ["$http", "$q", "ENV", function($http, $q, ENV) { var _baseUrl = function(minionIp) { var minionPort = ENV['/']['cAdvisorPort'] || "8081"; var proxy = ENV['/']['cAdvisorProxy'] || "/api/v1beta2/proxy/nodes/"; return proxy + minionIp + ':' + minionPort + '/api/v1.0/'; }; this.getMachineInfo = getMachineInfo; function getMachineInfo(minionIp) { var fullUrl = _baseUrl(minionIp) + 'machine'; var deferred = $q.defer(); // hack $http.get(fullUrl).success(function(data) { deferred.resolve(data); }).error(function(data, status) { deferred.reject('There was an error') }); return deferred.promise; } this.getContainerInfo = getContainerInfo; // containerId optional function getContainerInfo(minionIp, containerId) { containerId = (typeof containerId === "undefined") ? "/" : containerId; var fullUrl = _baseUrl(minionIp) + 'containers' + containerId; var deferred = $q.defer(); var request = { "num_stats": 10, "num_samples": 0 }; $http.post(fullUrl, request) .success(function(data) { deferred.resolve(data); }) .error(function() { deferred.reject('There was an error') }); return deferred.promise; } this.getDataForMinion = function(minionIp) { var machineData, containerData; var deferred = $q.defer(); var p = $q.all([getMachineInfo(minionIp), getContainerInfo(minionIp)]) .then( function(dataArray) { machineData = dataArray[0]; containerData = dataArray[1]; var memoryData = parseMemory(machineData, containerData); var cpuData = parseCpu(machineData, containerData); var fsData = parseFilesystems(machineData, containerData); deferred.resolve({ memoryData: memoryData, cpuData: cpuData, filesystemData: fsData, machineData: machineData, containerData: containerData }); }, function(errorData) { deferred.reject(errorData); }); return deferred.promise; }; // Utils to process cadvisor data function humanize(num, size, units) { var unit; for (unit = units.pop(); units.length && num >= size; unit = units.pop()) { num /= size; } return [num, unit]; } // Following the IEC naming convention function humanizeIEC(num) { var ret = humanize(num, 1024, ["TiB", "GiB", "MiB", "KiB", "Bytes"]); return ret[0].toFixed(2) + " " + ret[1]; } // Following the Metric naming convention function humanizeMetric(num) { var ret = humanize(num, 1000, ["TB", "GB", "MB", "KB", "Bytes"]); return ret[0].toFixed(2) + " " + ret[1]; } function hasResource(stats, resource) { return stats.stats.length > 0 && stats.stats[0][resource]; } // Gets the length of the interval in nanoseconds. function getInterval(current, previous) { var cur = new Date(current); var prev = new Date(previous); // ms -> ns. return (cur.getTime() - prev.getTime()) * 1000000; } function parseCpu(machineInfo, containerInfo) { var cur = containerInfo.stats[containerInfo.stats.length - 1]; var results = []; var cpuUsage = 0; if (containerInfo.spec.has_cpu && containerInfo.stats.length >= 2) { var prev = containerInfo.stats[containerInfo.stats.length - 2]; var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total; var intervalInNs = getInterval(cur.timestamp, prev.timestamp); // Convert to millicores and take the percentage cpuUsage = Math.round(((rawUsage / intervalInNs) / machineInfo.num_cores) * 100); if (cpuUsage > 100) { cpuUsage = 100; } } return { cpuPercentUsage: cpuUsage }; } function parseFilesystems(machineInfo, containerInfo) { var cur = containerInfo.stats[containerInfo.stats.length - 1]; if (!cur.filesystem) { return; } var filesystemData = []; for (var i = 0; i < cur.filesystem.length; i++) { var data = cur.filesystem[i]; var totalUsage = Math.floor((data.usage * 100.0) / data.capacity); var f = { device: data.device, filesystemNumber: i + 1, usage: data.usage, usageDescription: humanizeMetric(data.usage), capacity: data.capacity, capacityDescription: humanizeMetric(data.capacity), totalUsage: Math.floor((data.usage * 100.0) / data.capacity) }; filesystemData.push(f); } return filesystemData; } var oneMegabyte = 1024 * 1024; var oneGigabyte = 1024 * oneMegabyte; function parseMemory(machineInfo, containerInfo) { if (containerInfo.spec.has_memory && !hasResource(containerInfo, "memory")) { return; } // var titles = ["Time", "Total", "Hot"]; var data = []; for (var i = 0; i < containerInfo.stats.length; i++) { var cur = containerInfo.stats[i]; var elements = []; elements.push(cur.timestamp); elements.push(cur.memory.usage / oneMegabyte); elements.push(cur.memory.working_set / oneMegabyte); data.push(elements); } // Get the memory limit, saturate to the machine size. var memory_limit = machineInfo.memory_capacity; if (containerInfo.spec.memory.limit && (containerInfo.spec.memory.limit < memory_limit)) { memory_limit = containerInfo.spec.memory.limit; } var cur = containerInfo.stats[containerInfo.stats.length - 1]; var r = { current: { memoryUsage: cur.memory.usage, workingMemoryUsage: cur.memory.working_set, memoryLimit: memory_limit, memoryUsageDescription: humanizeMetric(cur.memory.usage), workingMemoryUsageDescription: humanizeMetric(cur.memory.working_set), memoryLimitDescription: humanizeMetric(memory_limit) }, historical: data }; return r; } }]); })(); app.provider('k8sApi', function() { var urlBase = ''; this.setUrlBase = function(value) { urlBase = value; }; var _get = function($http, baseUrl, query) { var _fullUrl = baseUrl; if (query !== undefined) { _fullUrl += '/' + query; } return $http.get(_fullUrl); }; this.$get = ["$http", "$q", function($http, $q) { var api = {}; api.getUrlBase = function() { return urlBase; }; api.getPods = function(query) { return _get($http, urlBase + '/pods', query); }; api.getMinions = function(query) { return _get($http, urlBase + '/minions', query); }; api.getServices = function(query) { return _get($http, urlBase + '/services', query); }; api.getReplicationControllers = function(query) { return _get($http, urlBase + '/replicationControllers', query) }; api.getEvents = function(query) { return _get($http, urlBase + '/events', query); }; return api; }]; }) .config(["k8sApiProvider", "ENV", function(k8sApiProvider, ENV) { if (ENV && ENV['/'] && ENV['/']['k8sApiServer']) { var proxy = ENV['/']['cAdvisorProxy'] || ''; k8sApiProvider.setUrlBase(proxy + ENV['/']['k8sApiServer']); } }]); (function() { "use strict"; var pollK8sDataServiceProvider = function PollK8sDataServiceProvider(_) { // A set of configuration controlling the polling behavior. // Their values should be configured in the application before // creating the service instance. var useSampleData = false; this.setUseSampleData = function(value) { useSampleData = value; }; var sampleDataFiles = ["shared/assets/sampleData1.json"]; this.setSampleDataFiles = function(value) { sampleDataFiles = value; }; var dataServer = "http://localhost:5555/cluster"; this.setDataServer = function(value) { dataServer = value; }; var pollMinIntervalSec = 10; this.setPollMinIntervalSec = function(value) { pollMinIntervalSec = value; }; var pollMaxIntervalSec = 120; this.setPollMaxIntervalSec = function(value) { pollMaxIntervalSec = value; }; var pollErrorThreshold = 5; this.setPollErrorThreshold = function(value) { pollErrorThreshold = value; }; this.$get = function($http, $timeout) { // Now the sequenceNumber will be used for debugging and verification purposes. var k8sdatamodel = { "data": undefined, "sequenceNumber": 0, "useSampleData": useSampleData }; var pollingError = 0; var promise = undefined; // Implement fibonacci back off when the service is down. var pollInterval = pollMinIntervalSec; var pollIncrement = pollInterval; // Reset polling interval. var resetCounters = function() { pollInterval = pollMinIntervalSec; pollIncrement = pollInterval; }; // Bump error count and polling interval. var bumpCounters = function() { // Bump the error count. pollingError++; // TODO: maybe display an error in the UI to the end user. if (pollingError % pollErrorThreshold === 0) { console.log("Error: " + pollingError + " consecutive polling errors for " + dataServer + "."); } // Bump the polling interval. var oldIncrement = pollIncrement; pollIncrement = pollInterval; pollInterval += oldIncrement; // Reset when limit reached. if (pollInterval > pollMaxIntervalSec) { resetCounters(); } }; var updateModel = function(newModel) { var dedupe = function(dataModel) { if (dataModel.resources) { dataModel.resources = _.uniq(dataModel.resources, function(resource) { return resource.id; }); } if (dataModel.relations) { dataModel.relations = _.uniq(dataModel.relations, function(relation) { return relation.source + relation.target; }); } }; dedupe(newModel); var newModelString = JSON.stringify(newModel); var oldModelString = ""; if (k8sdatamodel.data) { oldModelString = JSON.stringify(k8sdatamodel.data); } if (newModelString !== oldModelString) { k8sdatamodel.data = newModel; k8sdatamodel.sequenceNumber++; } pollingError = 0; resetCounters(); }; var nextSampleDataFile = 0; var getSampleDataFile = function() { var result = ""; if (sampleDataFiles.length > 0) { result = sampleDataFiles[nextSampleDataFile % sampleDataFiles.length]; ++nextSampleDataFile; } return result; }; var pollOnce = function(scope, repeat) { var dataSource = (k8sdatamodel.useSampleData) ? getSampleDataFile() : dataServer; $.getJSON(dataSource) .done(function(newModel, jqxhr, textStatus) { if (newModel && newModel.success) { delete newModel.success; // Remove success indicator. delete newModel.timestamp; // Remove changing timestamp. updateModel(newModel); scope.$apply(); promise = repeat ? $timeout(function() { pollOnce(scope, true); }, pollInterval * 1000) : undefined; return; } bumpCounters(); promise = repeat ? $timeout(function() { pollOnce(scope, true); }, pollInterval * 1000) : undefined; }) .fail(function(jqxhr, textStatus, error) { bumpCounters(); promise = repeat ? $timeout(function() { pollOnce(scope, true); }, pollInterval * 1000) : undefined; }); }; var isPolling = function() { return promise ? true : false; }; var start = function(scope) { // If polling has already started, then calling start() again would // just reset the counters and polling interval, but it will not // start a new thread polling in parallel to the existing polling // thread. resetCounters(); if (!promise) { k8sdatamodel.data = undefined; pollOnce(scope, true); } }; var stop = function() { if (promise) { $timeout.cancel(promise); promise = undefined; } }; var refresh = function(scope) { stop(scope); resetCounters(); k8sdatamodel.data = undefined; pollOnce(scope, false); }; return { "k8sdatamodel": k8sdatamodel, "isPolling": isPolling, "refresh": refresh, "start": start, "stop": stop }; }; }; angular.module("kubernetesApp.services") .provider("pollK8sDataService", ["lodash", pollK8sDataServiceProvider]) .config(["pollK8sDataServiceProvider", "ENV", function(pollK8sDataServiceProvider, ENV) { if (ENV && ENV['/']) { if (ENV['/']['k8sDataServer']) { pollK8sDataServiceProvider.setDataServer(ENV['/']['k8sDataServer']); } if (ENV['/']['k8sDataPollIntervalMinSec']) { pollK8sDataServiceProvider.setPollIntervalSec(ENV['/']['k8sDataPollIntervalMinSec']); } if (ENV['/']['k8sDataPollIntervalMaxSec']) { pollK8sDataServiceProvider.setPollIntervalSec(ENV['/']['k8sDataPollIntervalMaxSec']); } if (ENV['/']['k8sDataPollErrorThreshold']) { pollK8sDataServiceProvider.setPollErrorThreshold(ENV['/']['k8sDataPollErrorThreshold']); } } }]); }()); /**========================================================= * Module: toggle-state.js * Services to share toggle state functionality =========================================================*/ app.controller('cAdvisorController', [ '$scope', '$routeParams', 'k8sApi', 'lodash', 'cAdvisorService', '$q', '$interval', function($scope, $routeParams, k8sApi, lodash, cAdvisorService, $q, $interval) { $scope.k8sApi = k8sApi; $scope.activeMinionDataById = {}; $scope.maxDataByById = {}; $scope.getData = function() { $scope.loading = true; k8sApi.getMinions().success(angular.bind(this, function(res) { $scope.minions = res; // console.log(res); var promises = lodash.map(res.items, function(m) { return cAdvisorService.getDataForMinion(m.id); }); $q.all(promises).then( function(dataArray) { lodash.each(dataArray, function(data, i) { var m = res.items[i]; var maxData = maxMemCpuInfo(m.id, data.memoryData, data.cpuData, data.filesystemData); // console.log("maxData", maxData); $scope.activeMinionDataById[m.id] = transformMemCpuInfo(data.memoryData, data.cpuData, data.filesystemData, maxData, m.hostIP) }); }, function(errorData) { // console.log("Error: " + errorData); $scope.loading = false; }); $scope.loading = false; })).error(angular.bind(this, this.handleError)); }; function getcAdvisorDataForMinion(m) { var p = cAdvisorService.getDataForMinion(m.hostIP); return p; } function handleError(data, status, headers, config) { // console.log("Error (" + status + "): " + data); $scope.loading = false; }; // d3 function getColorForIndex(i, percentage) { // var colors = ['red', 'blue', 'yellow', 'pink', 'purple', 'green', 'orange']; // return colors[i]; var c = "color-" + (i + 1); if (percentage && percentage >= 90) c = c + ' color-critical'; else if (percentage && percentage >= 80) c = c + ' color-warning'; return c; } function getMaxColorForIndex(i, percentage) { // var colors = ['red', 'blue', 'yellow', 'pink', 'purple', 'green', 'orange']; // return colors[i]; var c = "color-max-" + (i + 1); if (percentage && percentage >= 90) c = c + ' color-max-critical'; else if (percentage && percentage >= 80) c = c + ' color-max-warning'; return c; } function maxMemCpuInfo(mId, mem, cpu, fsDataArray) { if ($scope.maxDataByById[mId] === undefined) $scope.maxDataByById[mId] = {}; var currentMem = mem.current; var currentCpu = cpu; var items = []; if ($scope.maxDataByById[mId]['cpu'] === undefined || $scope.maxDataByById[mId]['cpu'] < currentCpu.cpuPercentUsage) { // console.log("New max cpu " + mId, $scope.maxDataByById[mId].cpu, currentCpu.cpuPercentUsage); $scope.maxDataByById[mId]['cpu'] = currentCpu.cpuPercentUsage; } items.push({ maxValue: $scope.maxDataByById[mId]['cpu'], maxTickClassNames: getColorForIndex(0, $scope.maxDataByById[mId]['cpu']), maxClassNames: getMaxColorForIndex(0, $scope.maxDataByById[mId]['cpu']) }); var memPercentage = Math.floor((currentMem.memoryUsage * 100.0) / currentMem.memoryLimit); if ($scope.maxDataByById[mId]['mem'] === undefined || $scope.maxDataByById[mId]['mem'] < memPercentage) $scope.maxDataByById[mId]['mem'] = memPercentage; items.push({ maxValue: $scope.maxDataByById[mId]['mem'], maxTickClassNames: getColorForIndex(1, $scope.maxDataByById[mId]['mem']), maxClassNames: getMaxColorForIndex(1, $scope.maxDataByById[mId]['mem']) }); for (var i = 0; i < fsDataArray.length; i++) { var f = fsDataArray[i]; var fid = 'FS #' + f.filesystemNumber; if ($scope.maxDataByById[mId][fid] === undefined || $scope.maxDataByById[mId][fid] < f.totalUsage) $scope.maxDataByById[mId][fid] = f.totalUsage; items.push({ maxValue: $scope.maxDataByById[mId][fid], maxTickClassNames: getColorForIndex(2 + i, $scope.maxDataByById[mId][fid]), maxClassNames: getMaxColorForIndex(2 + i, $scope.maxDataByById[mId][fid]) }); } // console.log("Max Data is now " + mId, $scope.maxDataByById[mId]); return items; } function transformMemCpuInfo(mem, cpu, fsDataArray, maxData, hostName) { var currentMem = mem.current; var currentCpu = cpu; var items = []; items.push({ label: 'CPU', stats: currentCpu.cpuPercentUsage + '%', value: currentCpu.cpuPercentUsage, classNames: getColorForIndex(0, currentCpu.cpuPercentUsage), maxData: maxData[0], hostName: hostName }); var memPercentage = Math.floor((currentMem.memoryUsage * 100.0) / currentMem.memoryLimit); items.push({ label: 'Memory', stats: currentMem.memoryUsageDescription + ' / ' + currentMem.memoryLimitDescription, value: memPercentage, classNames: getColorForIndex(1, memPercentage), maxData: maxData[1], hostName: hostName }); for (var i = 0; i < fsDataArray.length; i++) { var f = fsDataArray[i]; items.push({ label: 'FS #' + f.filesystemNumber, stats: f.usageDescription + ' / ' + f.capacityDescription, value: f.totalUsage, classNames: getColorForIndex(2 + i, f.totalUsage), maxData: maxData[2 + i], hostName: hostName }); } var a = []; var segments = { segments: items }; a.push(segments); return a; }; // end d3 var promise = $interval($scope.getData, 3000); // Cancel interval on page changes $scope.$on('$destroy', function() { if (angular.isDefined(promise)) { $interval.cancel(promise); promise = undefined; } }); $scope.getData(); } ]); /**========================================================= * Module: Dashboard * Visualizer for clusters =========================================================*/ app.controller('DashboardCtrl', ['$scope', function($scope) {}]); /**========================================================= * Module: Group * Visualizer for groups =========================================================*/ app.controller('GroupCtrl', [ '$scope', '$route', '$interval', '$routeParams', 'k8sApi', '$rootScope', '$location', 'lodash', function($scope, $route, $interval, $routeParams, k8sApi, $rootScope, $location, _) { 'use strict'; $scope.doTheBack = function() { window.history.back(); }; $scope.capitalize = function(s) { return _.capitalize(s); }; $rootScope.doTheBack = $scope.doTheBack; $scope.resetGroupLayout = function(group) { delete group.settings; }; $scope.handlePath = function(path) { var parts = path.split("/"); // split leaves an empty string at the beginning. parts = parts.slice(1); if (parts.length === 0) { return; } this.handleGroups(parts.slice(1)); }; $scope.getState = function(obj) { return Object.keys(obj)[0]; }; $scope.clearSelector = function(grouping) { $location.path("/dashboard/groups/" + grouping + "/selector/"); }; $scope.changeGroupBy = function() { var grouping = $scope.selectedGroupBy; var s = _.clone($location.search()); if ($scope.routeParams.grouping != grouping) $location.path("/dashboard/groups/" + grouping + "/selector/").search(s); }; $scope.createBarrier = function(count, callback) { var barrier = count; var barrierFunction = angular.bind(this, function(data) { // JavaScript is single threaded so this is safe. barrier--; if (barrier === 0) { if (callback) { callback(); } } }); return barrierFunction; }; $scope.handleGroups = function(parts, selector) { $scope.groupBy = parts; $scope.loading = true; $scope.selector = selector; var args = []; var type = ""; if (selector && selector.length > 0) { $scope.selectorPieces = selector.split(","); var labels = []; var fields = []; for (var i = 0; i < $scope.selectorPieces.length; i++) { var piece = $scope.selectorPieces[i]; if (piece[0] == '$') { fields.push(piece.slice(2)); } else { if (piece.indexOf("type=") === 0) { var labelParts = piece.split("="); if (labelParts.length > 1) { type = labelParts[1]; } } else { labels.push(piece); } } } if (labels.length > 0) { args.push("labels=" + encodeURI(labels.join(","))); } if (fields.length > 0) { args.push("fields=" + encodeURI(fields.join(","))); } } var query = "?" + args.join("&"); var list = []; var count = type.length > 0 ? 1 : 3; var barrier = $scope.createBarrier(count, function() { $scope.groups = $scope.groupData(list, 0); $scope.loading = false; $scope.groupByOptions = buildGroupByOptions(); $scope.selectedGroupBy = $routeParams.grouping; }); if (type === "" || type == "pod") { k8sApi.getPods(query).success(function(data) { $scope.addLabel("type", "pod", data.items); for (var i = 0; data.items && i < data.items.length; ++i) { data.items[i].labels.host = data.items[i].currentState.host; list.push(data.items[i]); } barrier(); }).error($scope.handleError); } if (type === "" || type == "service") { k8sApi.getServices(query).success(function(data) { $scope.addLabel("type", "service", data.items); for (var i = 0; data.items && i < data.items.length; ++i) { list.push(data.items[i]); } barrier(); }).error($scope.handleError); } if (type === "" || type == "replicationController") { k8sApi.getReplicationControllers(query).success(angular.bind(this, function(data) { $scope.addLabel("type", "replicationController", data.items); for (var i = 0; data.items && i < data.items.length; ++i) { list.push(data.items[i]); } barrier(); })).error($scope.handleError); } }; $scope.addLabel = function(key, value, items) { if (!items) { return; } for (var i = 0; i < items.length; i++) { if (!items[i].labels) { items[i].labels = []; } items[i].labels[key] = value; } }; $scope.groupData = function(items, index) { var result = { "items": {}, "kind": "grouping" }; for (var i = 0; i < items.length; i++) { key = items[i].labels[$scope.groupBy[index]]; if (!key) { key = ""; } var list = result.items[key]; if (!list) { list = []; result.items[key] = list; } list.push(items[i]); } if (index + 1 < $scope.groupBy.length) { for (var key in result.items) { result.items[key] = $scope.groupData(result.items[key], index + 1); } } return result; }; $scope.getGroupColor = function(type) { if (type === 'pod') { return '#6193F0'; } else if (type === 'replicationController') { return '#E008FE'; } else if (type === 'service') { return '#7C43FF'; } }; var groups = $routeParams.grouping; if (!groups) { groups = ''; } $scope.routeParams = $routeParams; $scope.route = $route; $scope.handleGroups(groups.split('/'), $routeParams.selector); $scope.handleError = function(data, status, headers, config) { console.log("Error (" + status + "): " + data); $scope_.loading = false; }; function getDefaultGroupByOptions() { return [{name: 'Type', value: 'type'}, {name: 'Name', value: 'name'}]; } function buildGroupByOptions() { var g = $scope.groups; var options = getDefaultGroupByOptions(); var newOptions = _.map(g.items, function(vals) { return _.map(vals, function(v) { return _.keys(v.labels); }); }); newOptions = _.reject(_.uniq(_.flattenDeep(newOptions)), function(o) { return o == 'name' || o == 'type' || o == ""; }); newOptions = _.map(newOptions, function(o) { return { name: o, value: o }; }); options = options.concat(newOptions); return options; } $scope.changeFilterBy = function(selector) { var grouping = $scope.selectedGroupBy; var s = _.clone($location.search()); if ($scope.routeParams.selector != selector) $location.path("/dashboard/groups/" + $scope.routeParams.grouping + "/selector/" + selector).search(s); }; } ]); /**========================================================= * Module: Header * Visualizer for clusters =========================================================*/ angular.module('kubernetesApp.components.dashboard', []) .controller('HeaderCtrl', [ '$scope', '$location', function($scope, $location) { 'use strict'; $scope.$watch('Pages', function(newValue, oldValue) { if (typeof newValue !== 'undefined') { $location.path(newValue); } }); $scope.subPages = [ {category: 'dashboard', name: 'Explore', value: '/dashboard/groups/type/selector/'}, {category: 'dashboard', name: 'Pods', value: '/dashboard/pods'}, {category: 'dashboard', name: 'Minions', value: '/dashboard/minions'}, {category: 'dashboard', name: 'Replication Controllers', value: '/dashboard/replicationcontrollers'}, {category: 'dashboard', name: 'Services', value: '/dashboard/services'}, {category: 'dashboard', name: 'Events', value: '/dashboard/events'} ]; } ]); /**========================================================= * Module: List Events * Visualizer list events =========================================================*/ app.controller('ListEventsCtrl', [ '$scope', '$routeParams', 'k8sApi', '$location', '$filter', function($scope, $routeParams, k8sApi, $location, $filter) { 'use strict'; $scope.getData = getData; $scope.loading = true; $scope.k8sApi = k8sApi; $scope.pods = null; $scope.groupedPods = null; $scope.serverView = false; $scope.headers = [ {name: 'Time', field: 'time'}, {name: 'From', field: 'from'}, {name: 'Sub Object Path', field: 'subobject'}, {name: 'Reason', field: 'reason'}, {name: 'Message', field: 'message'} ]; $scope.custom = { time: '', from: 'grey', subobject: 'grey', reason: 'grey', message: 'grey' }; $scope.sortable = ['time', 'from', 'subobject']; $scope.thumbs = 'thumb'; $scope.count = 10; $scope.go = function(d) { $location.path('/dashboard/pods/' + d.id); }; $scope.moreClick = function(d, e) { $location.path('/dashboard/pods/' + d.id); e.stopPropagation(); }; function handleError(data, status, headers, config) { console.log("Error (" + status + "): " + data); $scope.loading = false; } $scope.content = []; function getData(dataId) { $scope.loading = true; k8sApi.getEvents().success(function(data) { $scope.loading = false; var _fixComma = function(str) { if (str.substring(0, 1) == ',') { return str.substring(1); } else { return str; } }; data.items.forEach(function(event) { $scope.content.push({ time: $filter('date')(event.timestamp, 'medium'), from: event.source, subobject: event.involvedObject.fieldPath, reason: event.reason, message: event.message }); }); }).error($scope.handleError); } getData($routeParams.serviceId); } ]); /**========================================================= * Module: Minions * Visualizer for minions =========================================================*/ app.controller('ListMinionsCtrl', [ '$scope', '$routeParams', 'k8sApi', '$location', function($scope, $routeParams, k8sApi, $location) { 'use strict'; $scope.getData = getData; $scope.loading = true; $scope.k8sApi = k8sApi; $scope.pods = null; $scope.groupedPods = null; $scope.serverView = false; $scope.headers = [{name: 'Name', field: 'name'}, {name: 'IP', field: 'ip'}, {name: 'Status', field: 'status'}]; $scope.custom = { name: '', status: 'grey', ip: 'grey' }; $scope.sortable = ['name', 'status', 'ip']; $scope.thumbs = 'thumb'; $scope.count = 10; $scope.go = function(d) { $location.path('/dashboard/pods/' + d.id); }; $scope.moreClick = function(d, e) { $location.path('/dashboard/pods/' + d.id); e.stopPropagation(); }; function handleError(data, status, headers, config) { console.log("Error (" + status + "): " + data); $scope.loading = false; } $scope.content = []; function getData(dataId) { $scope.loading = true; k8sApi.getMinions().success(function(data) { $scope.loading = false; var _fixComma = function(str) { if (str.substring(0, 1) == ',') { return str.substring(1); } else { return str; } }; data.items.forEach(function(minion) { var _kind = ''; if (minion.status.conditions) { Object.keys(minion.status.conditions) .forEach(function(key) { _kind += minion.status.conditions[key].kind; }); } $scope.content.push({name: minion.id, ip: minion.hostIP, status: _kind}); }); }).error($scope.handleError); } getData($routeParams.serviceId); } ]); app.controller('ListPodsCtrl', [ '$scope', '$routeParams', 'k8sApi', 'lodash', '$location', function($scope, $routeParams, k8sApi, lodash, $location) { var _ = lodash; $scope.getData = getData; $scope.loading = true; $scope.k8sApi = k8sApi; $scope.pods = null; $scope.groupedPods = null; $scope.serverView = false; $scope.headers = [ {name: '', field: 'thumb'}, {name: 'Pod', field: 'pod'}, {name: 'IP', field: 'ip'}, {name: 'Status', field: 'status'}, {name: 'Containers', field: 'containers'}, {name: 'Images', field: 'images'}, {name: 'Host', field: 'host'}, {name: 'Labels', field: 'labels'} ]; $scope.custom = { pod: '', ip: 'grey', containers: 'grey', images: 'grey', host: 'grey', labels: 'grey', status: 'grey' }; $scope.sortable = ['pod', 'ip', 'status']; $scope.thumbs = 'thumb'; $scope.count = 10; $scope.go = function(d) { $location.path('/dashboard/pods/' + d.id); }; $scope.moreClick = function(d, e) { $location.path('/dashboard/pods/' + d.id); e.stopPropagation(); }; var orderedPodNames = []; function handleError(data, status, headers, config) { console.log("Error (" + status + "): " + data); $scope.loading = false; }; function getPodName(pod) { return _.has(pod.labels, 'name') ? pod.labels.name : pod.id; } $scope.content = []; function getData(dataId) { $scope.loading = true; k8sApi.getPods().success(angular.bind(this, function(data) { $scope.loading = false; var _fixComma = function(str) { if (str.substring(0, 1) == ',') { return str.substring(1); } else { return str; } }; data.items.forEach(function(pod) { var _containers = '', _images = '', _labels = '', _uses = ''; if (pod.desiredState.manifest) { Object.keys(pod.desiredState.manifest.containers) .forEach(function(key) { _containers += ', ' + pod.desiredState.manifest.containers[key].name; _images += ', ' + pod.desiredState.manifest.containers[key].image; }); } Object.keys(pod.labels) .forEach(function(key) { if (key == 'name') { _labels += ', ' + pod.labels[key]; } if (key == 'uses') { _uses += ', ' + pod.labels[key]; } }); $scope.content.push({ thumb: '"assets/img/kubernetes.svg"', pod: pod.id, ip: pod.currentState.podIP, containers: _fixComma(_containers), images: _fixComma(_images), host: pod.currentState.host, labels: _fixComma(_labels) + ':' + _fixComma(_uses), status: pod.currentState.status }); }); })).error(angular.bind(this, handleError)); }; $scope.getPodRestarts = function(pod) { var r = null; var container = _.first(pod.desiredState.manifest.containers); if (container) r = pod.currentState.info[container.name].restartCount; return r; }; $scope.otherLabels = function(labels) { return _.omit(labels, 'name') }; $scope.podStatusClass = function(pod) { var s = pod.currentState.status.toLowerCase(); if (s == 'running' || s == 'succeeded') return null; else return "status-" + s; }; $scope.podIndexFromName = function(pod) { var name = getPodName(pod); return _.indexOf(orderedPodNames, name) + 1; }; getData($routeParams.serviceId); } ]); /**========================================================= * Module: Replication Controllers * Visualizer for replication controllers =========================================================*/ app.controller('ListReplicationControllersCtrl', [ '$scope', '$routeParams', 'k8sApi', '$location', function($scope, $routeParams, k8sApi, $location) { 'use strict'; $scope.getData = getData; $scope.loading = true; $scope.k8sApi = k8sApi; $scope.pods = null; $scope.groupedPods = null; $scope.serverView = false; $scope.headers = [ {name: 'Controller', field: 'controller'}, {name: 'Containers', field: 'containers'}, {name: 'Images', field: 'images'}, {name: 'Selector', field: 'selector'}, {name: 'Replicas', field: 'replicas'} ]; $scope.custom = { controller: '', containers: 'grey', images: 'grey', selector: 'grey', replicas: 'grey' }; $scope.sortable = ['controller', 'containers', 'images']; $scope.thumbs = 'thumb'; $scope.count = 10; $scope.go = function(d) { $location.path('/dashboard/pods/' + d.id); }; $scope.moreClick = function(d, e) { $location.path('/dashboard/pods/' + d.id); e.stopPropagation(); }; function handleError(data, status, headers, config) { console.log("Error (" + status + "): " + data); $scope.loading = false; } $scope.content = []; function getData(dataId) { $scope.loading = true; k8sApi.getReplicationControllers().success(function(data) { $scope.loading = false; var _fixComma = function(str) { if (str.substring(0, 1) == ',') { return str.substring(1); } else { return str; } }; data.items.forEach(function(replicationController) { var _name = '', _image = ''; if (replicationController.desiredState.podTemplate.desiredState.manifest.containers) { Object.keys(replicationController.desiredState.podTemplate.desiredState.manifest.containers) .forEach(function(key) { _name += replicationController.desiredState.podTemplate.desiredState.manifest.containers[key].name; _image += replicationController.desiredState.podTemplate.desiredState.manifest.containers[key].image; }); } var _name_selector = ''; if (replicationController.desiredState.replicaSelector) { Object.keys(replicationController.desiredState.replicaSelector) .forEach(function(key) { _name_selector += replicationController.desiredState.replicaSelector[key]; }); } $scope.content.push({ controller: replicationController.id, containers: _name, images: _image, selector: _name_selector, replicas: replicationController.currentState.replicas }); }); }).error($scope.handleError); } getData($routeParams.serviceId); } ]); /**========================================================= * Module: Services * Visualizer for services =========================================================*/ app.controller('ListServicesCtrl', [ '$scope', '$interval', '$routeParams', 'k8sApi', '$rootScope', function($scope, $interval, $routeParams, k8sApi, $rootScope) { 'use strict'; $scope.doTheBack = function() { window.history.back(); }; $scope.headers = [ {name: 'Name', field: 'name'}, {name: 'Labels', field: 'labels'}, {name: 'Selector', field: 'selector'}, {name: 'IP', field: 'ip'}, {name: 'Port', field: 'port'} ]; $scope.custom = { name: '', ip: 'grey', selector: 'grey', port: 'grey', labels: 'grey' }; $scope.sortable = ['name', 'ip', 'port']; $scope.count = 10; $scope.content = []; $rootScope.doTheBack = $scope.doTheBack; $scope.handleError = function(data, status, headers, config) { console.log("Error (" + status + "): " + data); $scope_.loading = false; }; $scope.getData = function(dataId) { $scope.loading = true; k8sApi.getServices(dataId).success(angular.bind(this, function(data) { $scope.services = data; $scope.loading = false; var _fixComma = function(str) { if (str.substring(0, 1) == ',') { return str.substring(1); } else { return str; } }; var addLabel = function(str, label) { if (str) { str = label + str; } return str; }; if (data.items.constructor === Array) { data.items.forEach(function(service) { var _name = '', _uses = '', _component = '', _provider = ''; if (service.labels !== null && typeof service.labels === 'object') { Object.keys(service.labels) .forEach(function(key) { if (key == 'name') { _name += ',' + service.labels[key]; } if (key == 'component') { _component += ',' + service.labels[key]; } if (key == 'provider') { _provider += ',' + service.labels[key]; } }); } var _selectors = ''; if (service.selector !== null && typeof service.selector === 'object') { Object.keys(service.selector) .forEach(function(key) { if (key == 'name') { _selectors += ',' + service.selector[key]; } }); } $scope.content.push({ name: service.id, ip: service.portalIP, port: service.port, selector: addLabel(_fixComma(_selectors), 'name='), labels: addLabel(_fixComma(_name), 'name=') + ' ' + addLabel(_fixComma(_component), 'component=') + ' ' + addLabel(_fixComma(_provider), 'provider=') }); }); } })).error($scope.handleError); }; $scope.getData($routeParams.serviceId); } ]); /**========================================================= * Module: Pods * Visualizer for pods =========================================================*/ app.controller('PodCtrl', [ '$scope', '$interval', '$routeParams', 'k8sApi', '$rootScope', function($scope, $interval, $routeParams, k8sApi, $rootScope) { 'use strict'; $scope.doTheBack = function() { window.history.back(); }; $rootScope.doTheBack = $scope.doTheBack; $scope.handleError = function(data, status, headers, config) { console.log("Error (" + status + "): " + data); $scope_.loading = false; }; $scope.handlePod = function(podId) { $scope.loading = true; k8sApi.getPods(podId).success(angular.bind(this, function(data) { $scope.pod = data; $scope.loading = false; })).error($scope.handleError); }; $scope.handlePod($routeParams.podId); } ]); /**========================================================= * Module: Replication * Visualizer for replication controllers =========================================================*/ function ReplicationController() { } ReplicationController.prototype.getData = function(dataId) { this.scope.loading = true; this.k8sApi.getReplicationControllers(dataId).success(angular.bind(this, function(data) { this.scope.replicationController = data; this.scope.loading = false; })).error(angular.bind(this, this.handleError)); }; ReplicationController.prototype.handleError = function(data, status, headers, config) { console.log("Error (" + status + "): " + data); this.scope.loading = false; }; app.controller('ReplicationControllerCtrl', [ '$scope', '$routeParams', 'k8sApi', function($scope, $routeParams, k8sApi) { $scope.controller = new ReplicationController(); $scope.controller.k8sApi = k8sApi; $scope.controller.scope = $scope; $scope.controller.getData($routeParams.replicationControllerId); } ]); /**========================================================= * Module: Services * Visualizer for services =========================================================*/ function ServiceController() { } ServiceController.prototype.getData = function(dataId) { this.scope.loading = true; this.k8sApi.getServices(dataId).success(angular.bind(this, function(data) { this.scope.service = data; this.scope.loading = false; })).error(angular.bind(this, this.handleError)); }; ServiceController.prototype.handleError = function(data, status, headers, config) { console.log("Error (" + status + "): " + data); this.scope.loading = false; }; app.controller('ServiceCtrl', [ '$scope', '$routeParams', 'k8sApi', '$location', function($scope, $routeParams, k8sApi, $location) { $scope.controller = new ServiceController(); $scope.controller.k8sApi = k8sApi; $scope.controller.scope = $scope; $scope.controller.getData($routeParams.serviceId); $scope.go = function(d) { $location.path('/dashboard/services/' + d.id); } $scope.moreClick = function(d, e) { $location.path('/dashboard/services/' + d.id); e.stopPropagation(); } } ]); (function() { 'use strict'; angular.module('kubernetesApp.components.dashboard') .directive('d3MinionBarGauge', [ 'd3DashboardService', function(d3DashboardService) { return { restrict: 'E', scope: { data: '=', thickness: '@', graphWidth: '@', graphHeight: '@' }, link: function(scope, element, attrs) { var draw = function(d3) { var svg = d3.select("svg.chart"); var legendSvg = d3.select("svg.legend"); window.onresize = function() { return scope.$apply(); }; scope.$watch(function() { return angular.element(window)[0].innerWidth; }, function() { return scope.render(scope.data); }); scope.$watch('data', function(newVals, oldVals) { return initOrUpdate(newVals, oldVals); }, true); function initOrUpdate(newVals, oldVals) { if (oldVals === null || oldVals === undefined) { return scope.render(newVals); } else { return update(oldVals, newVals); } } var textOffset = 10; var el = null; var radius = 100; var oldData = []; function init(options) { var clone = options.data; var preparedData = setData(clone); setup(preparedData, options.width, options.height); } function setup(data, w, h) { svg = d3.select(element[0]).append("svg").attr("width", "100%"); legendSvg = d3.select(element[0]).append("svg").attr("width", "100%"); var chart = svg.attr("class", "chart") .attr("width", w) .attr("height", h - 25) .append("svg:g") .attr("class", "concentricchart") .attr("transform", "translate(" + ((w / 2)) + "," + h / 4 + ")"); var legend = legendSvg.attr("class", "legend").attr("width", w); radius = Math.min(w, h) / 2; var hostName = legendSvg.append("text") .attr("class", "hostName") .attr("transform", "translate(" + ((w - 120) / 2) + "," + 15 + ")"); var label_legend_area = legendSvg.append("svg:g") .attr("class", "label_legend_area") .attr("transform", "translate(" + ((w - 185) / 2) + "," + 35 + ")"); var legend_group = label_legend_area.append("svg:g").attr("class", "legend_group"); var label_group = label_legend_area.append("svg:g") .attr("class", "label_group") .attr("transform", "translate(" + 25 + "," + 11 + ")"); var stats_group = label_legend_area.append("svg:g") .attr("class", "stats_group") .attr("transform", "translate(" + 85 + "," + 11 + ")"); var path_group = chart.append("svg:g") .attr("class", "path_group") .attr("transform", "translate(0," + (h / 4) + ")"); var value_group = chart.append("svg:g") .attr("class", "value_group") .attr("transform", "translate(" + -(w * 0.205) + "," + -(h * 0.10) + ")"); generateArcs(chart, data); } function update(_oldData, _newData) { if (_newData === undefined || _newData === null) { return; } var clone = jQuery.extend(true, {}, _newData); var cloneOld = jQuery.extend(true, {}, _oldData); var preparedData = setData(clone); oldData = setData(cloneOld); animate(preparedData); } function animate(data) { generateArcs(null, data); } function setData(data) { var diameter = 2 * Math.PI * radius; var localData = []; $.each(data[0].segments, function(ri, value) { function calcAngles(v) { var segmentValueSum = 200; if (v > segmentValueSum) { v = segmentValueSum; } var segmentValue = v; var fraction = segmentValue / segmentValueSum; var arcBatchLength = fraction * 4 * Math.PI; var arcPartition = arcBatchLength; var startAngle = Math.PI * 2; var endAngle = startAngle + arcPartition; return { startAngle: startAngle, endAngle: endAngle }; } var valueData = calcAngles(value.value); data[0].segments[ri].startAngle = valueData.startAngle; data[0].segments[ri].endAngle = valueData.endAngle; var maxData = value.maxData; var maxTickData = calcAngles(maxData.maxValue + 0.2); data[0].segments[ri].maxTickStartAngle = maxTickData.startAngle; data[0].segments[ri].maxTickEndAngle = maxTickData.endAngle; var maxArcData = calcAngles(maxData.maxValue); data[0].segments[ri].maxArcStartAngle = maxArcData.startAngle; data[0].segments[ri].maxArcEndAngle = maxArcData.endAngle; data[0].segments[ri].index = ri; }); localData.push(data[0].segments); return localData[0]; } function generateArcs(_svg, data) { var chart = svg; var transitionTime = 750; $.each(data, function(index, value) { if (oldData[index] !== undefined) { data[index].previousEndAngle = oldData[index].endAngle; } else { data[index].previousEndAngle = 0; } }); var thickness = parseInt(scope.thickness, 10); var ir = (parseInt(scope.graphWidth, 10) / 3); var path_group = svg.select('.path_group'); var arc_group = path_group.selectAll(".arc_group").data(data); var arcEnter = arc_group.enter().append("g").attr("class", "arc_group"); arcEnter.append("path").attr("class", "bg-circle").attr("d", getBackgroundArc(thickness, ir)); arcEnter.append("path") .attr("class", function(d, i) { return 'max_tick_arc ' + d.maxData.maxTickClassNames; }); arcEnter.append("path") .attr("class", function(d, i) { return 'max_bg_arc ' + d.maxData.maxClassNames; }); arcEnter.append("path").attr("class", function(d, i) { return 'value_arc ' + d.classNames; }); var max_tick_arc = arc_group.select(".max_tick_arc"); max_tick_arc.transition() .attr("class", function(d, i) { return 'max_tick_arc ' + d.maxData.maxTickClassNames; }) .attr("d", function(d) { var arc = maxArc(thickness, ir); arc.startAngle(d.maxTickStartAngle); arc.endAngle(d.maxTickEndAngle); return arc(d); }); var max_bg_arc = arc_group.select(".max_bg_arc"); max_bg_arc.transition() .attr("class", function(d, i) { return 'max_bg_arc ' + d.maxData.maxClassNames; }) .attr("d", function(d) { var arc = maxArc(thickness, ir); arc.startAngle(d.maxArcStartAngle); arc.endAngle(d.maxArcEndAngle); return arc(d); }); var value_arc = arc_group.select(".value_arc"); value_arc.transition().ease("exp").attr("class", function(d, i) { return 'value_arc ' + d.classNames; }).duration(transitionTime).attrTween("d", function(d) { return arcTween(d, thickness, ir); }); arc_group.exit() .select(".value_arc") .transition() .ease("exp") .duration(transitionTime) .attrTween("d", function(d) { return arcTween(d, thickness, ir); }) .remove(); drawLabels(chart, data, ir, thickness); buildLegend(chart, data); } function arcTween(b, thickness, ir) { var prev = JSON.parse(JSON.stringify(b)); prev.endAngle = b.previousEndAngle; var i = d3.interpolate(prev, b); return function(t) { return getArc(thickness, ir)(i(t)); }; } function maxArc(thickness, ir) { var arc = d3.svg.arc().innerRadius(function(d) { return getRadiusRing(ir, d.index); }).outerRadius(function(d) { return getRadiusRing(ir + thickness, d.index); }); return arc; } function drawLabels(chart, data, ir, thickness) { svg.select('.value_group').selectAll("*").remove(); var counts = data.length; var value_group = chart.select('.value_group'); var valueLabels = value_group.selectAll("text.value").data(data); valueLabels.enter() .append("svg:text") .attr("class", "value") .attr( "transform", function(d) { return "translate(" + (getRadiusRing(ir, counts - 1)) + ", 0)"; }) .attr("dx", function(d, i) { return 0; }) .attr("dy", function(d, i) { return (thickness + 3) * i; }) .attr("text-anchor", function(d) { return "start"; }) .text(function(d) { return d.value; }); valueLabels.transition().duration(300).attrTween( "d", function(d) { return arcTween(d, thickness, ir); }); valueLabels.exit().remove(); } function buildLegend(chart, data) { var svg = legendSvg; svg.select('.label_group').selectAll("*").remove(); svg.select('.legend_group').selectAll("*").remove(); svg.select('.stats_group').selectAll("*").remove(); var host_name = svg.select('.hostName'); var label_group = svg.select('.label_group'); var stats_group = svg.select('.stats_group'); host_name.text(data[0].hostName); host_name = svg.selectAll("text.hostName").data(data); host_name.attr("text-anchor", function(d) { return "start"; }) .text(function(d) { return d.hostName; }); host_name.exit().remove(); var labels = label_group.selectAll("text.labels").data(data); labels.enter() .append("svg:text") .attr("class", "labels") .attr("dy", function(d, i) { return 19 * i; }) .attr("text-anchor", function(d) { return "start"; }) .text(function(d) { return d.label; }); labels.exit().remove(); var stats = stats_group.selectAll("text.stats").data(data); stats.enter() .append("svg:text") .attr("class", "stats") .attr("dy", function(d, i) { return 19 * i; }) .attr("text-anchor", function(d) { return "start"; }) .text(function(d) { return d.stats; }); stats.exit().remove(); var legend_group = svg.select('.legend_group'); var legend = legend_group.selectAll("rect").data(data); legend.enter() .append("svg:rect") .attr("x", 2) .attr("y", function(d, i) { return 19 * i; }) .attr("width", 13) .attr("height", 13) .attr("class", function(d, i) { return "rect " + d.classNames; }); legend.exit().remove(); } function getRadiusRing(ir, i) { return ir - (i * 20); } function getArc(thickness, ir) { var arc = d3.svg.arc() .innerRadius(function(d) { return getRadiusRing(ir, d.index); }) .outerRadius(function(d) { return getRadiusRing(ir + thickness, d.index); }) .startAngle(function(d, i) { return d.startAngle; }) .endAngle(function(d, i) { return d.endAngle; }); return arc; } function getBackgroundArc(thickness, ir) { var arc = d3.svg.arc() .innerRadius(function(d) { return getRadiusRing(ir, d.index); }) .outerRadius(function(d) { return getRadiusRing(ir + thickness, d.index); }) .startAngle(0) .endAngle(function() { return 2 * Math.PI; }); return arc; } scope.render = function(data) { if (data === undefined || data === null) { return; } svg.selectAll("*").remove(); var graph = $(element[0]); var w = scope.graphWidth; var h = scope.graphHeight; var options = { data: data, width: w, height: h }; init(options); }; }; d3DashboardService.d3().then(draw); } }; } ]); }()); (function() { 'use strict'; angular.module('kubernetesApp.components.dashboard') .directive( 'dashboardHeader', function() { 'use strict'; return { restrict: 'A', replace: true, scope: {user: '='}, templateUrl: "components/dashboard/pages/header.html", controller: [ '$scope', '$filter', '$location', '$rootScope', function($scope, $filter, $location, $rootScope) { $scope.$watch('page', function(newValue, oldValue) { if (typeof newValue !== 'undefined') { $location.path(newValue); } }); $scope.subpages = [ { category: 'dashboard', name: 'Groups', value: '/dashboard/groups/type/selector/', id: 'groupsView' }, {category: 'dashboard', name: 'Pods', value: '/dashboard/pods', id: 'podsView'}, {category: 'dashboard', name: 'Minions', value: '/dashboard/minions', id: 'minionsView'}, { category: 'dashboard', name: 'Replication Controllers', value: '/dashboard/replicationcontrollers', id: 'rcView' }, {category: 'dashboard', name: 'Services', value: '/dashboard/services', id: 'servicesView'}, {category: 'dashboard', name: 'Events', value: '/dashboard/events', id: 'eventsView'}, ]; } ] }; }) .directive('dashboardFooter', function() { 'use strict'; return { restrict: 'A', replace: true, templateUrl: "components/dashboard/pages/footer.html", controller: ['$scope', '$filter', function($scope, $filter) {}] }; }) .directive('mdTable', function() { 'use strict'; return { restrict: 'E', scope: { headers: '=', content: '=', sortable: '=', filters: '=', customClass: '=customClass', thumbs: '=', count: '=' }, controller: ["$scope", "$filter", "$window", "$location", function($scope, $filter, $window, $location) { var orderBy = $filter('orderBy'); $scope.currentPage = 0; $scope.nbOfPages = function() { return Math.ceil($scope.content.length / $scope.count); }; $scope.handleSort = function(field) { if ($scope.sortable.indexOf(field) > -1) { return true; } else { return false; } }; $scope.go = function(d) { if (d.pod) { $location.path('/dashboard/pods/' + d.pod); } else if (d.name) { $location.path('/dashboard/services/' + d.name); } }; $scope.order = function(predicate, reverse) { $scope.content = orderBy($scope.content, predicate, reverse); $scope.predicate = predicate; }; $scope.order($scope.sortable[0], false); $scope.getNumber = function(num) { return new Array(num); }; $scope.goToPage = function(page) { $scope.currentPage = page; }; }], templateUrl: 'views/partials/md-table.tmpl.html' }; }); }()); angular.module('kubernetesApp.components.dashboard') .factory('d3DashboardService', [ '$document', '$q', '$rootScope', function($document, $q, $rootScope) { var d = $q.defer(); function onScriptLoad() { // Load client in the browser $rootScope.$apply(function() { d.resolve(window.d3); }); } // Create a script tag with d3 as the source // and call our onScriptLoad callback when it // has been loaded var scriptTag = $document[0].createElement('script'); scriptTag.type = 'text/javascript'; scriptTag.async = true; scriptTag.src = 'vendor/d3/d3.min.js'; scriptTag.onreadystatechange = function() { if (this.readyState == 'complete') onScriptLoad(); }; scriptTag.onload = onScriptLoad; var s = $document[0].getElementsByTagName('body')[0]; s.appendChild(scriptTag); return { d3: function() { return d.promise; } }; } ]); (function() { 'use strict'; angular.module('pods', []).service('podService', PodDataService); /** * Pod DataService * Mock async data service. * * @returns {{loadAll: Function}} * @constructor */ function PodDataService($q) { var pods = { "kind": "PodList", "creationTimestamp": null, "selfLink": "/api/v1beta1/pods", "resourceVersion": 166552, "apiVersion": "v1beta1", "items": [{ "id": "hello", "uid": "0fe3644e-ab53-11e4-8ae8-061695c59fcf", "creationTimestamp": "2015-02-03T03:16:36Z", "selfLink": "/api/v1beta1/pods/hello?namespace=default", "resourceVersion": 466, "namespace": "default", "labels": {"environment": "testing", "name": "hello"}, "desiredState": { "manifest": { "version": "v1beta2", "id": "", "volumes": null, "containers": [{ "name": "hello", "image": "quay.io/kelseyhightower/hello", "ports": [{"hostPort": 80, "containerPort": 80, "protocol": "TCP"}], "imagePullPolicy": "PullIfNotPresent" }], "restartPolicy": {"always": {}}, "dnsPolicy": "ClusterFirst" } }, "currentState": { "manifest": {"version": "", "id": "", "volumes": null, "containers": null, "restartPolicy": {}}, "status": "Running", "host": "172.31.12.204", "podIP": "10.244.73.2", "info": { "hello": { "state": {"running": {"startedAt": "2015-02-03T03:16:51Z"}}, "restartCount": 0, "image": "quay.io/kelseyhightower/hello", "containerID": "docker://96ade8ff30a44c4489969eaf343a7899317671b07a9766ecd0963e9b41501256" }, "net": { "state": {"running": {"startedAt": "2015-02-03T03:16:41Z"}}, "restartCount": 0, "podIP": "10.244.73.2", "image": "kubernetes/pause:latest", "containerID": "docker://93d32603cafbff7165dadb1d4527899c24246bca2f5e6770b8297fd3721b272c" } } } }] }; // Uses promises return { loadAll: function() { // Simulate async call return $q.when(pods); } }; } PodDataService.$inject = ["$q"]; })(); (function() { 'use strict'; angular.module('replicationControllers', []) .service('replicationControllerService', ReplicationControllerDataService); /** * Replication Controller DataService * Mock async data service. * * @returns {{loadAll: Function}} * @constructor */ function ReplicationControllerDataService($q) { var replicationControllers = { "kind": "ReplicationControllerList", "creationTimestamp": null, "selfLink": "/api/v1beta1/replicationControllers", "resourceVersion": 166552, "apiVersion": "v1beta1", "items": [] }; // Uses promises return { loadAll: function() { // Simulate async call return $q.when(replicationControllers); } }; } ReplicationControllerDataService.$inject = ["$q"]; })(); (function() { 'use strict'; angular.module('services', []).service('serviceService', ServiceDataService); /** * Service DataService * Mock async data service. * * @returns {{loadAll: Function}} * @constructor */ function ServiceDataService($q) { var services = { "kind": "ServiceList", "creationTimestamp": null, "selfLink": "/api/v1beta1/services", "resourceVersion": 166552, "apiVersion": "v1beta1", "items": [ { "id": "kubernetes", "uid": "626dd08d-ab51-11e4-8ae8-061695c59fcf", "creationTimestamp": "2015-02-03T03:04:36Z", "selfLink": "/api/v1beta1/services/kubernetes?namespace=default", "resourceVersion": 11, "namespace": "default", "port": 443, "protocol": "TCP", "labels": {"component": "apiserver", "provider": "kubernetes"}, "selector": null, "containerPort": 0, "portalIP": "10.244.66.215", "sessionAffinity": "None" }, { "id": "kubernetes-ro", "uid": "626f9584-ab51-11e4-8ae8-061695c59fcf", "creationTimestamp": "2015-02-03T03:04:36Z", "selfLink": "/api/v1beta1/services/kubernetes-ro?namespace=default", "resourceVersion": 12, "namespace": "default", "port": 80, "protocol": "TCP", "labels": {"component": "apiserver", "provider": "kubernetes"}, "selector": null, "containerPort": 0, "portalIP": "10.244.182.142", "sessionAffinity": "None" } ] }; // Uses promises return { loadAll: function() { // Simulate async call return $q.when(services); } }; } ServiceDataService.$inject = ["$q"]; })();