mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-25 12:17:52 +00:00
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:
11993
www/master/shared/assets/sampleData1.json
Normal file
11993
www/master/shared/assets/sampleData1.json
Normal file
File diff suppressed because it is too large
Load Diff
9
www/master/shared/config/development.example.json
Normal file
9
www/master/shared/config/development.example.json
Normal 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"
|
||||
}
|
9
www/master/shared/config/development.json
Normal file
9
www/master/shared/config/development.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"k8sApiServer": "/api/v1beta2",
|
||||
"k8sDataServer": "/cluster",
|
||||
"k8sDataPollMinIntervalSec": 10,
|
||||
"k8sDataPollMaxIntervalSec": 120,
|
||||
"k8sDataPollErrorThreshold": 5,
|
||||
"cAdvisorProxy": "",
|
||||
"cAdvisorPort": "4194"
|
||||
}
|
17
www/master/shared/config/generated-config.js
Normal file
17
www/master/shared/config/generated-config.js
Normal 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)
|
||||
|
||||
;
|
4
www/master/shared/js/modules/config.js
Normal file
4
www/master/shared/js/modules/config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/**=========================================================
|
||||
* Module: config.js
|
||||
* App routes and resources configuration
|
||||
=========================================================*/
|
4
www/master/shared/js/modules/constants.js
Normal file
4
www/master/shared/js/modules/constants.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/**=========================================================
|
||||
* Module: constants.js
|
||||
* Define constants to inject across the application
|
||||
=========================================================*/
|
4
www/master/shared/js/modules/controllers/main.js
Normal file
4
www/master/shared/js/modules/controllers/main.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/**=========================================================
|
||||
* Module: main.js
|
||||
* Main Application Controller
|
||||
=========================================================*/
|
25
www/master/shared/js/modules/controllers/tabs-global.js
Normal file
25
www/master/shared/js/modules/controllers/tabs-global.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
4
www/master/shared/js/modules/directives/sidebar.js
Normal file
4
www/master/shared/js/modules/directives/sidebar.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/**=========================================================
|
||||
* Module: sidebar.js
|
||||
* Wraps the sidebar and handles collapsed state
|
||||
=========================================================*/
|
195
www/master/shared/js/modules/services/cAdvisor.js
Normal file
195
www/master/shared/js/modules/services/cAdvisor.js
Normal 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;
|
||||
}
|
||||
});
|
||||
})();
|
42
www/master/shared/js/modules/services/k8sApiService.js
Normal file
42
www/master/shared/js/modules/services/k8sApiService.js
Normal 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']);
|
||||
}
|
||||
});
|
187
www/master/shared/js/modules/services/pollK8sData.js
Normal file
187
www/master/shared/js/modules/services/pollK8sData.js
Normal 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']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
4
www/master/shared/js/modules/services/toggle-state.js
Normal file
4
www/master/shared/js/modules/services/toggle-state.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/**=========================================================
|
||||
* Module: toggle-state.js
|
||||
* Services to share toggle state functionality
|
||||
=========================================================*/
|
Reference in New Issue
Block a user