Files
kubernetes/www/app/assets/js/app.js
Patrick Reilly 716d98c39e initial commit of chrome only of new replacement web ui
remove node modules

make new data file for web ui

initial commit of dashboard

switch back to non SSL request

move port splitting to common place; add to node resource location

Signed-off-by: Patrick Reilly <patrick@kismatic.io>

various path fixes

make svg path relative

work around missing mime type

Signed-off-by: Patrick Reilly <patrick@kismatic.io>

fix paths

fix karma path

remove bad protractor test
2015-04-21 07:24:06 -07:00

2360 lines
78 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));
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 = '<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":"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"];
})();