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
This commit is contained in:
Patrick Reilly
2015-04-19 04:45:14 -07:00
parent 27daa29753
commit 716d98c39e
182 changed files with 51954 additions and 2746 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
{
"k8sApiServer": "ENV_K8S_API_SERVER",
"k8sDataServer": "ENV_K8S_DATA_SERVER",
"k8sDataPollMinIntervalSec": "ENV_K8S_DATA_POLL_MIN_INTERVAL_SEC",
"k8sDataPollMaxIntervalSec": "ENV_K8S_DATA_POLL_MAX_INTERVAL_SEC",
"k8sDataPollErrorThreshold": "ENV_K8S_DATA_POLL_ERROR_THRESHOLD",
"cAdvisorProxy": "ENV_C_ADVISOR_PROXY",
"cAdvisorPort": "ENV_C_ADVISOR_PORT"
}

View File

@@ -0,0 +1,9 @@
{
"k8sApiServer": "/api/v1beta2",
"k8sDataServer": "/cluster",
"k8sDataPollMinIntervalSec": 10,
"k8sDataPollMaxIntervalSec": 120,
"k8sDataPollErrorThreshold": 5,
"cAdvisorProxy": "",
"cAdvisorPort": "4194"
}

View File

@@ -0,0 +1,17 @@
angular.module("kubernetesApp.config", [])
.constant("ENV", {
"/": {
"k8sApiServer": "/api/v1beta2",
"k8sDataServer": "/cluster",
"k8sDataPollMinIntervalSec": 10,
"k8sDataPollMaxIntervalSec": 120,
"k8sDataPollErrorThreshold": 5,
"cAdvisorProxy": "",
"cAdvisorPort": "4194"
}
})
.constant("ngConstant", true)
;

View File

@@ -0,0 +1,4 @@
/**=========================================================
* Module: config.js
* App routes and resources configuration
=========================================================*/

View File

@@ -0,0 +1,4 @@
/**=========================================================
* Module: constants.js
* Define constants to inject across the application
=========================================================*/

View File

@@ -0,0 +1,4 @@
/**=========================================================
* Module: main.js
* Main Application Controller
=========================================================*/

View File

@@ -0,0 +1,25 @@
/**=========================================================
* 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);
}
}
};
}
]);

View File

@@ -0,0 +1,4 @@
/**=========================================================
* Module: sidebar.js
* Wraps the sidebar and handles collapsed state
=========================================================*/

View File

@@ -0,0 +1,195 @@
(function() {
"use strict";
angular.module('kubernetesApp.services')
.service('cAdvisorService', 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;
}
});
})();

View File

@@ -0,0 +1,42 @@
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 = 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(function(k8sApiProvider, ENV) {
if (ENV && ENV['/'] && ENV['/']['k8sApiServer']) {
var proxy = ENV['/']['cAdvisorProxy'] || '';
k8sApiProvider.setUrlBase(proxy + ENV['/']['k8sApiServer']);
}
});

View File

@@ -0,0 +1,187 @@
(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(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']);
}
}
});
}());

View File

@@ -0,0 +1,4 @@
/**=========================================================
* Module: toggle-state.js
* Services to share toggle state functionality
=========================================================*/