kubernetes/www/app/assets/js/app.js
BC Broussard 288c1df4e4 Update Web UI tables
- Condense table styling
- Default table size to 50
- Make all columns sortable
- Rename service portalIP to clusterIP
- Allow default descending table ordering
2015-07-02 08:43:37 -07:00

2710 lines
90 KiB
JavaScript

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/<component-name>/js
// ****************************
// -----------------------------------
var app = angular.module('kubernetesApp', [
'ngRoute',
'ngMaterial',
'ngLodash',
'door3.css',
'kubernetesApp.config',
'kubernetesApp.services',
'angular.filter'
].concat(componentNamespaces));
app.factory('menu', [
'$location',
'$rootScope',
'sections',
'$route',
function($location, $rootScope, sections, $route) {
var self;
$rootScope.$on('$locationChangeSuccess', onLocationChange);
return self = {
sections: sections,
setSections: function(_sections) { this.sections = _sections; },
selectSection: function(section) { self.openedSection = section; },
toggleSelectSection: function(section) {
self.openedSection = (self.openedSection === section ? null : section);
},
isSectionSelected: function(section) { return self.openedSection === section; },
selectPage: function(section, page) {
self.currentSection = section;
self.currentPage = page;
},
isPageSelected: function(page) { return self.currentPage === page; }
};
function onLocationChange() {
var path = $route.current.originalPath;
var matchPage = function(section, page) {
if (path === page.url || path === (page.url + '/')) {
self.selectSection(section);
self.selectPage(section, page);
}
};
sections.forEach(function(section) {
if (section.children) {
section.children.forEach(function(childSection) {
if (childSection.pages) {
childSection.pages.forEach(function(page) { matchPage(childSection, page); });
}
});
} else if (section.pages) {
section.pages.forEach(function(page) { matchPage(section, page); });
} else if (section.type === 'link') {
matchPage(section, section);
}
});
}
}
]);
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.value("sections", [{"name":"Dashboard","url":"/dashboard","type":"link","templateUrl":"/components/dashboard/pages/home.html"},{"name":"Dashboard","type":"heading","children":[{"name":"Dashboard","type":"toggle","url":"/dashboard","templateUrl":"/components/dashboard/pages/home.html","pages":[{"name":"Pods","url":"/dashboard/pods","templateUrl":"/components/dashboard/views/listPods.html","type":"link"},{"name":"Pod Visualizer","url":"/dashboard/visualpods","templateUrl":"/components/dashboard/views/listPodsVisualizer.html","type":"link"},{"name":"Services","url":"/dashboard/services","templateUrl":"/components/dashboard/views/listServices.html","type":"link"},{"name":"Replication Controllers","url":"/dashboard/replicationcontrollers","templateUrl":"/components/dashboard/views/listReplicationControllers.html","type":"link"},{"name":"Events","url":"/dashboard/events","templateUrl":"/components/dashboard/views/listEvents.html","type":"link"},{"name":"Nodes","url":"/dashboard/nodes","templateUrl":"/components/dashboard/views/listMinions.html","type":"link"},{"name":"Replication Controller","url":"/dashboard/replicationcontrollers/:replicationControllerId","templateUrl":"/components/dashboard/views/replication.html","type":"link"},{"name":"Service","url":"/dashboard/services/:serviceId","templateUrl":"/components/dashboard/views/service.html","type":"link"},{"name": "Node","url": "/dashboard/nodes/:nodeId","templateUrl": "/components/dashboard/views/node.html","type": "link"},{"name":"Explore","url":"/dashboard/groups/:grouping*?/selector/:selector*?","templateUrl":"/components/dashboard/views/groups.html","type":"link"},{"name":"Pod","url":"/dashboard/pods/:podId","templateUrl":"/components/dashboard/views/pod.html","type":"link"}]}]},{"name":"Graph","url":"/graph","type":"link","templateUrl":"/components/graph/pages/home.html"},{"name":"Graph","url":"/graph/inspect","type":"link","templateUrl":"/components/graph/pages/inspect.html","css":"/components/graph/css/show-details-table.css"},{"name":"Graph","type":"heading","children":[{"name":"Graph","type":"toggle","url":"/graph","templateUrl":"/components/graph/pages/home.html","pages":[{"name":"Test","url":"/graph/test","type":"link","templateUrl":"/components/graph/pages/home.html"}]}]}]);
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 = '<div layout="column">' + _entries + '</div>';
}
};
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":"Nodes","url":"/dashboard/nodes","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":"Node","url":"/dashboard/nodes/:nodeId","templateUrl":"components/dashboard/views/node.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/v1",
"k8sDataServer": "",
"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: home-page.js
* Page Controller
=========================================================*/
app.controller('PageCtrl', [
'$scope',
'$timeout',
'$mdSidenav',
'menu',
'$rootScope',
function($scope, $timeout, $mdSidenav, menu, $rootScope) {
$scope.menu = menu;
$scope.path = path;
$scope.goHome = goHome;
$scope.openMenu = openMenu;
$rootScope.openMenu = openMenu;
$scope.closeMenu = closeMenu;
$scope.isSectionSelected = isSectionSelected;
$rootScope.$on('$locationChangeSuccess', openPage);
// Methods used by menuLink and menuToggle directives
this.isOpen = isOpen;
this.isSelected = isSelected;
this.toggleOpen = toggleOpen;
this.shouldLockOpen = shouldLockOpen;
$scope.toggleKubernetesUiMenu = toggleKubernetesUiMenu;
var mainContentArea = document.querySelector("[role='main']");
var kubernetesUiMenu = document.querySelector("[role='kubernetes-ui-menu']");
// *********************
// Internal methods
// *********************
var _t = false;
$scope.showKubernetesUiMenu = false;
function shouldLockOpen() {
return _t;
}
function toggleKubernetesUiMenu() {
$scope.showKubernetesUiMenu = !$scope.showKubernetesUiMenu;
}
function closeMenu() {
$timeout(function() {
$mdSidenav('left').close();
});
}
function openMenu() {
$timeout(function() {
_t = !$mdSidenav('left').isOpen();
$mdSidenav('left').toggle();
});
}
function path() {
return $location.path();
}
function goHome($event) {
menu.selectPage(null, null);
$location.path( '/' );
}
function openPage() {
$scope.closeMenu();
mainContentArea.focus();
}
function isSelected(page) {
return menu.isPageSelected(page);
}
function isSectionSelected(section) {
var selected = false;
var openedSection = menu.openedSection;
if(openedSection === section){
selected = true;
}
else if(section.children) {
section.children.forEach(function(childSection) {
if(childSection === openedSection){
selected = true;
}
});
}
return selected;
}
function isOpen(section) {
return menu.isSectionSelected(section);
}
function toggleOpen(section) {
menu.toggleSelectSection(section);
}
}
]).filter('humanizeDoc', function() {
return function(doc) {
if (!doc) return;
if (doc.type === 'directive') {
return doc.name.replace(/([A-Z])/g, function($1) {
return '-'+$1.toLowerCase();
});
}
return doc.label || doc.name;
}; });
/**=========================================================
* 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/v1/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 = '';
var _namespace = 'default';
this.setUrlBase = function(value) { urlBase = value; };
this.setNamespace = function(value) { _namespace = value; };
this.getNamespace = function() { return _namespace; };
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 + '/namespaces/' + _namespace; };
api.getPods = function(query) { return _get($http, api.getUrlBase() + '/pods', query); };
api.getNodes = function(query) { return _get($http, urlBase + '/nodes', query); };
api.getMinions = api.getNodes;
api.getServices = function(query) { return _get($http, api.getUrlBase() + '/services', query); };
api.getReplicationControllers = function(query) {
return _get($http, api.getUrlBase() + '/replicationcontrollers', query)
};
api.getEvents = function(query) { return _get($http, api.getUrlBase() + '/events', query); };
return api;
}];
})
.config(["k8sApiProvider", "ENV", function(k8sApiProvider, ENV) {
if (ENV && ENV['/'] && ENV['/']['k8sApiServer']) {
k8sApiProvider.setUrlBase(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.metadata.name); });
$q.all(promises).then(
function(dataArray) {
lodash.each(dataArray, function(data, i) {
var m = res.items[i];
var maxData = maxMemCpuInfo(m.metadata.name, data.memoryData, data.cpuData, data.filesystemData);
// console.log("maxData", maxData);
var hostname = "";
if(m.status.addresses)
hostname = m.status.addresses[0].address;
$scope.activeMinionDataById[m.metadata.name] =
transformMemCpuInfo(data.memoryData, data.cpuData, data.filesystemData, maxData, hostname);
});
},
function(errorData) {
// console.log("Error: " + errorData);
$scope.loading = false;
});
$scope.loading = false;
})).error(angular.bind(this, this.handleError));
};
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: 'Filesystem #' + 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 = encodeURIComponent($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;
$scope.selectorName = decodeURIComponent(selector);
var args = [];
var type = "";
var selectedHost = "";
if (selector && selector.length > 0) {
$scope.selectorPieces = selector.split(",");
var labels = [];
var fields = [];
for (var i = 0; i < $scope.selectorPieces.length; i++) {
var piece = decodeURIComponent($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 if (piece.indexOf("host=") === 0){
var labelParts = piece.split("=");
if (labelParts.length > 1) {
selectedHost = labelParts[1];
}
}
else {
labels.push(piece);
}
}
}
if (labels.length > 0) {
args.push("labelSelector=" + 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;
$scope.selectedGroupByName = decodeURIComponent($routeParams.grouping)
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].metadata.labels.host = data.items[i].spec.nodeName;
if(selectedHost.length == 0 || selectedHost == data.items[i].metadata.labels.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].metadata.labels) {
items[i].metadata.labels = {};
}
items[i].metadata.labels[key] = value;
}
};
$scope.groupData = function(items, index) {
var result = {
"items": {},
"kind": "grouping"
};
for (var i = 0; i < items.length; i++) {
key = items[i].metadata.labels[decodeURIComponent($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.metadata.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: 'Nodes', value: '/dashboard/nodes'},
{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: 'Last Seen', field: 'lastSeen'},
{name: 'First Seen', field: 'firstSeen'},
{name: 'Count', field: 'count'},
{name: 'Name', field: 'name'},
{name: 'Kind', field: 'kind'},
{name: 'SubObject', field: 'subObject'},
{name: 'Reason', field: 'reason'},
{name: 'Source', field: 'source'},
{name: 'Message', field: 'message'}
];
$scope.sortable = ['lastSeen', 'firstSeen', 'count', 'name', 'kind', 'subObject', 'reason', 'source', 'message'];
$scope.count = 50;
function handleError(data, status, headers, config) {
console.log("Error (" + status + "): " + data);
$scope.loading = false;
}
$scope.content = [];
function getData() {
$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) {
var _sources = '';
if (event.source) {
_sources = event.source.component + ' ' + event.source.host;
}
$scope.content.push({
firstSeen: $filter('date')(event.firstTimestamp, 'medium'),
lastSeen: $filter('date')(event.lastTimestamp, 'medium'),
count: event.count,
name: event.involvedObject.name,
kind: event.involvedObject.kind,
subObject: event.involvedObject.fieldPath,
reason: event.reason,
source: _sources,
message: event.message
});
});
$scope.content = _.sortBy($scope.content, function(e){
return e.lastSeen;
}).reverse();
}).error($scope.handleError);
}
getData();
}
]);
/**=========================================================
* 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: 'Addresses', field: 'addresses'}, {name: 'Status', field: 'status'}];
$scope.custom = {
name: '',
status: 'grey',
ip: 'grey'
};
$scope.sortable = ['name', 'status', 'addresses'];
$scope.thumbs = 'thumb';
$scope.count = 50;
$scope.go = function(d) { $location.path('/dashboard/nodes/' + d.name); };
function handleError(data, status, headers, config) {
console.log("Error (" + status + "): " + data);
$scope.loading = false;
}
$scope.content = [];
function getData() {
$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 _statusType = '';
if (minion.status.conditions) {
Object.keys(minion.status.conditions)
.forEach(function(key) { _statusType += minion.status.conditions[key].type; });
}
$scope.content.push({name: minion.metadata.name, addresses: _.map(minion.status.addresses, function(a) { return a.address }).join(', '), status: _statusType});
});
}).error($scope.handleError);
}
getData();
}
]);
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: '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','containers','images','host','labels'];
$scope.count = 50;
$scope.go = function(data) { $location.path('/dashboard/pods/' + data.pod); };
var orderedPodNames = [];
function handleError(data, status, headers, config) {
console.log("Error (" + status + "): " + data);
$scope.loading = false;
};
function getPodName(pod) { return _.has(pod.metadata.labels, 'name') ? pod.metadata.labels.name : pod.metadata.name; }
$scope.content = [];
function getData() {
$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.spec) {
Object.keys(pod.spec.containers)
.forEach(function(key) {
_containers += ', ' + pod.spec.containers[key].name;
_images += ', ' + pod.spec.containers[key].image;
});
}
if (pod.metadata.labels) {
_labels = _.map(pod.metadata.labels, function(v, k) { return k + ': ' + v }).join(', ');
}
$scope.content.push({
pod: pod.metadata.name,
ip: pod.status.podIP,
containers: _fixComma(_containers),
images: _fixComma(_images),
host: pod.spec.nodeName,
labels: _labels,
status: pod.status.phase
});
});
})).error(angular.bind(this, handleError));
};
$scope.getPodRestarts = function(pod) {
var r = null;
var container = _.first(pod.spec.containers);
if (container) r = pod.status.containerStatuses[container.name].restartCount;
return r;
};
$scope.otherLabels = function(labels) { return _.omit(labels, 'name') };
$scope.podStatusClass = function(pod) {
var s = pod.status.phase.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();
}
]);
/**=========================================================
* 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', 'selector', 'replicas'];
$scope.thumbs = 'thumb';
$scope.count = 50;
$scope.go = function(data) { $location.path('/dashboard/replicationcontrollers/' + data.controller); };
function handleError(data, status, headers, config) {
console.log("Error (" + status + "): " + data);
$scope.loading = false;
}
$scope.content = [];
function getData() {
$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.spec.template.spec.containers) {
Object.keys(replicationController.spec.template.spec.containers)
.forEach(function(key) {
_name += replicationController.spec.template.spec.containers[key].name;
_image += replicationController.spec.template.spec.containers[key].image;
});
}
var _selectors = '';
if (replicationController.spec.selector) {
_selectors = _.map(replicationController.spec.selector, function(v, k) { return k + '=' + v }).join(', ');
}
$scope.content.push({
controller: replicationController.metadata.name,
containers: _name,
images: _image,
selector: _selectors,
replicas: replicationController.status.replicas
});
});
}).error($scope.handleError);
}
getData();
}
]);
/**=========================================================
* Module: Services
* Visualizer for services
=========================================================*/
app.controller('ListServicesCtrl', [
'$scope',
'$interval',
'$routeParams',
'k8sApi',
'$rootScope',
'$location',
function($scope, $interval, $routeParams, k8sApi, $rootScope, $location) {
'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: 'Ports', field: 'port'}
];
$scope.custom = {
name: '',
ip: 'grey',
selector: 'grey',
port: 'grey',
labels: 'grey'
};
$scope.sortable = ['name', 'ip', 'port', 'labels', 'selector'];
$scope.count = 50;
$scope.go = function(data) { $location.path('/dashboard/services/' + data.name); };
$scope.content = [];
$rootScope.doTheBack = $scope.doTheBack;
$scope.handleError = function(data, status, headers, config) {
console.log("Error (" + status + "): " + data);
$scope_.loading = false;
};
$scope.getData = function() {
$scope.loading = true;
k8sApi.getServices().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 _labels = '';
if (service.metadata.labels) {
_labels = _.map(service.metadata.labels, function(v, k) { return k + ': ' + v }).join(', ');
}
var _selectors = '';
if (service.spec.selector) {
_selectors = _.map(service.spec.selector, function(v, k) { return k + '=' + v }).join(', ');
}
var _ports = '';
if (service.spec.ports) {
_ports = _.map(service.spec.ports, function(p) {
var n = '';
if(p.name)
n = p.name + ': ';
n = n + p.port;
return n;
}).join(', ');
}
$scope.content.push({
name: service.metadata.name,
ip: service.spec.clusterIP,
port: _ports,
selector: _selectors,
labels: _labels
});
});
}
})).error($scope.handleError);
};
$scope.getData();
}
]);
/**=========================================================
* Module: Nodes
* Visualizer for nodes
=========================================================*/
app.controller('NodeCtrl', [
'$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.handleNode = function(nodeId) {
$scope.loading = true;
k8sApi.getNodes(nodeId).success(angular.bind(this, function(data) {
$scope.node = data;
$scope.loading = false;
})).error($scope.handleError);
};
$scope.handleNode($routeParams.nodeId);
}
]);
/**=========================================================
* 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);
$scope.doTheBack = function() { window.history.back(); };
$scope.getSelectorUrlFragment = function(sel){ return _.map(sel, function(v, k) { return k + '=' + v }).join(','); };
}
]);
/**=========================================================
* 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.doTheBack = function() { window.history.back(); };
$scope.go = function(d) { $location.path('/dashboard/services/' + d.metadata.name); }
$scope.getSelectorUrlFragment = function(sel){ return _.map(sel, function(v, k) { return k + '=' + v }).join(','); };
}
]);
(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 - 215) / 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(" + 115 + "," + 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("dx", function(d, i) { return 68; })
.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;
}
d3.select(element[0]).select("svg.chart").remove();
d3.select(element[0]).select("svg.legend").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',
'menu',
'$rootScope',
function($scope, $filter, $location, menu, $rootScope) {
$scope.menu = menu;
$scope.$watch('page', function(newValue, oldValue) {
if (typeof newValue !== 'undefined') {
$location.path(newValue);
}
});
$scope.subpages = [
{
category: 'dashboard',
name: 'Explore',
value: '/dashboard/groups/type/selector/',
id: 'groupsView'
},
{category: 'dashboard', name: 'Pods', value: '/dashboard/pods', id: 'podsView'},
{category: 'dashboard', name: 'Nodes', value: '/dashboard/nodes', 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: '=',
reverse: '=',
doSelect: '&onSelect'
},
transclude: true,
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.order = function(predicate, reverse) {
$scope.content = orderBy($scope.content, predicate, reverse);
$scope.predicate = predicate;
};
var reverse = false;
if($scope.reverse)
reverse = $scope.reverse;
$scope.order($scope.sortable[0], reverse);
$scope.getNumber = function(num) { return new Array(num); };
$scope.goToPage = function(page) { $scope.currentPage = page; };
$scope.showMore = function() { return angular.isDefined($scope.moreClick);}
}],
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": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "redis-master-c0r1n",
"generateName": "redis-master-",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/pods/redis-master-c0r1n",
"uid": "f12ddfaf-ff77-11e4-8f2d-080027213276",
"resourceVersion": "39",
"creationTimestamp": "2015-05-21T05:12:14Z",
"labels": {
"name": "redis-master"
},
"annotations": {
"kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"ReplicationController\",\"namespace\":\"default\",\"name\":\"redis-master\",\"uid\":\"f12969e0-ff77-11e4-8f2d-080027213276\",\"apiVersion\":\"v1\",\"resourceVersion\":\"26\"}}"
}
},
"spec": {
"volumes": [
{
"name": "default-token-zb4rq",
"secret": {
"secretName": "default-token-zb4rq"
}
}
],
"containers": [
{
"name": "master",
"image": "redis",
"ports": [
{
"containerPort": 6379,
"protocol": "TCP"
}
],
"resources": {},
"volumeMounts": [
{
"name": "default-token-zb4rq",
"readOnly": true,
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
}
],
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "IfNotPresent",
"capabilities": {},
"securityContext": {
"capabilities": {},
"privileged": false
}
}
],
"restartPolicy": "Always",
"dnsPolicy": "ClusterFirst",
"serviceAccount": "default",
"host": "127.0.0.1"
},
"status": {
"phase": "Running",
"Condition": [
{
"type": "Ready",
"status": "True"
}
],
"hostIP": "127.0.0.1",
"podIP": "172.17.0.1",
"startTime": "2015-05-21T05:12:14Z",
"containerStatuses": [
{
"name": "master",
"state": {
"running": {
"startedAt": "2015-05-21T05:12:14Z"
}
},
"lastState": {},
"ready": true,
"restartCount": 0,
"image": "redis",
"imageID": "docker://95af5842ddb9b03f7c6ec7601e65924cec516fcedd7e590ae31660057085cf67",
"containerID": "docker://ae2a1e0a91a8b1015191a0b8e2ce8c55a86fb1a9a2b1e8e3b29430c9d93c8c09"
}
]
}
};
// 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": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "ReplicationController",
"apiVersion": "v1",
"metadata": {
"name": "redis-master",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/replicationcontrollers/redis-master",
"uid": "f12969e0-ff77-11e4-8f2d-080027213276",
"resourceVersion": "28",
"creationTimestamp": "2015-05-21T05:12:14Z",
"labels": {
"name": "redis-master"
}
},
"spec": {
"replicas": 1,
"selector": {
"name": "redis-master"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"name": "redis-master"
}
},
"spec": {
"containers": [
{
"name": "master",
"image": "redis",
"ports": [
{
"containerPort": 6379,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "IfNotPresent",
"capabilities": {},
"securityContext": {
"capabilities": {},
"privileged": false
}
}
],
"restartPolicy": "Always",
"dnsPolicy": "ClusterFirst",
"serviceAccount": ""
}
}
},
"status": {
"replicas": 1
}
}
]};
// 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": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "kubernetes",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/services/kubernetes",
"resourceVersion": "6",
"creationTimestamp": null,
"labels": {
"component": "apiserver",
"provider": "kubernetes"
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 443,
"targetPort": 443
}
],
"portalIP": "10.0.0.2",
"sessionAffinity": "None"
},
"status": {}
},
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "kubernetes-ro",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/services/kubernetes-ro",
"resourceVersion": "8",
"creationTimestamp": null,
"labels": {
"component": "apiserver",
"provider": "kubernetes"
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 80,
"targetPort": 80
}
],
"portalIP": "10.0.0.1",
"sessionAffinity": "None"
},
"status": {}
},
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "redis-master",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/services/redis-master",
"uid": "a6fde246-ff78-11e4-8f2d-080027213276",
"resourceVersion": "72",
"creationTimestamp": "2015-05-21T05:17:19Z",
"labels": {
"name": "redis-master"
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 6379,
"targetPort": 6379
}
],
"selector": {
"name": "redis-master"
},
"portalIP": "10.0.0.124",
"sessionAffinity": "None"
},
"status": {}
}
]
};
// Uses promises
return {
loadAll: function() {
// Simulate async call
return $q.when(services);
}
};
}
ServiceDataService.$inject = ["$q"];
})();