
- Condense table styling - Default table size to 50 - Make all columns sortable - Rename service portalIP to clusterIP - Allow default descending table ordering
2710 lines
90 KiB
JavaScript
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"];
|
|
|
|
})();
|