cockpit/pkg/kubernetes/scripts/containers.js

343 lines
13 KiB
JavaScript

/*
* This file is part of Cockpit.
*
* Copyright (C) 2015 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
var angular = require('angular');
require('angular-route');
require('angular-dialog.js');
require('./kube-client');
require('./listing');
require('kubernetes-container-terminal/dist/container-terminal.js');
angular.module('kubernetes.containers', [
'ngRoute',
'ui.cockpit',
'kubernetesUI',
'kubeClient',
'kubernetes.listing'
])
.config([
'$routeProvider',
function($routeProvider) {
$routeProvider
.when('/pods/:pod_namespace/:pod_name/:container_name', {
templateUrl: 'views/container-page.html',
controller: 'ContainerCtrl'
})
.when('/pods/:pod_namespace/:pod_name', {
redirectTo: '/pods'
})
.when('/pods/:pod_namespace?', {
templateUrl: 'views/containers-page.html',
controller: 'ContainersCtrl'
});
}
])
/*
* The controller for the containers view.
*/
.controller('ContainersCtrl', [
'$scope',
'KubeContainers',
'kubeLoader',
'kubeSelect',
'ListingState',
'$routeParams',
'$location',
function($scope, containers, loader, select, ListingState, $routeParams, $location) {
var selector = {};
var qs = $location.search();
for (var key in qs) {
if (key !== "namespace")
selector[key] = qs[key];
}
loader.listen(function() {
var pods = select().kind("Pod");
if ($routeParams.pod_namespace)
pods.namespace($routeParams.pod_namespace);
if (!angular.equals({}, selector))
pods.label(selector);
$scope.pods = pods;
}, $scope);
loader.watch("Pod", $scope);
$scope.listing = new ListingState($scope);
$scope.containers = containers;
$scope.$on("activate", function(ev, id) {
ev.preventDefault();
$location.path(id);
});
$scope.should_mask = function(name) {
return name.toLowerCase().indexOf("password") !== -1;
};
}
])
/*
* The controller for the containers view.
*/
.controller('ContainerCtrl', [
'$scope',
'KubeContainers',
'kubeLoader',
'kubeSelect',
'$routeParams',
'$route',
function($scope, containers, loader, select, $routeParams, $route) {
var target = $routeParams["container_name"] || "";
$scope.target = target;
loader.listen(function() {
$scope.pod = select().kind("Pod")
.namespace($routeParams.pod_namespace || "")
.name($routeParams.pod_name || "")
.one();
if ($scope.pod) {
angular.forEach(containers($scope.pod) || [], function (con) {
if (con.spec && con.spec.name === target)
$scope.container = con;
});
}
}, $scope);
loader.watch("Pod", $scope);
$scope.back = function() {
$route.updateParams({ "container_name" : undefined });
};
$scope.should_mask = function(name) {
return name.toLowerCase().indexOf("password") !== -1;
};
}
])
/**
* Build an array of container objects where each object contains the data from both
* the spec and status sections of the pod. Looks like this:
* { id: id, spec: pod.spec.containers[n], status: pod.status.containerStatuses[n] }
*
* The returned array will not change once created for a given pod item.
*/
.factory('KubeContainers', [
'KubeMapNamedArray',
function(mapNamedArray) {
return function (item) {
var specs, statuses, pod_id;
if (!item.containers) {
pod_id = "pods/" + item.metadata.namespace + "/" + item.metadata.name;
if (item.spec)
specs = mapNamedArray(item.spec.containers);
else
specs = { };
if (item.status)
statuses = mapNamedArray(item.status.containerStatuses);
else
statuses = { };
item.containers = Object.keys(specs).map(function(name) {
var key = pod_id + "/" + name;
return { spec: specs[name], status: statuses[name], key: key };
});
}
return item.containers;
};
}
])
.directive('containersListing',
function() {
return {
restrict: 'A',
templateUrl: 'views/containers-listing.html'
};
}
)
.directive('containerPageInline',
function() {
return {
restrict: 'A',
templateUrl: 'views/container-page-inline.html'
};
}
)
.directive('kubeContainerBody',
function() {
return {
restrict: 'E',
templateUrl: 'views/container-body.html'
};
}
)
.directive('kubePodBody',
function() {
return {
restrict: 'E',
templateUrl: 'views/pod-body.html'
};
}
)
/*
* Displays a container console.
*
* <kube-console namespace="ns" container="name"></kube-console>
*/
.directive('kubeConsole', [
'kubernetesContainerSocket',
function(socket) {
return {
restrict: 'E',
scope: {
pod: '&',
container: '&',
command: '@',
prevent: '='
},
link: function(scope, element, attrs) {
var limit = 64 * 1024;
var outer = angular.element("<div>");
outer.addClass("console-ct");
element.append(outer);
var pre = angular.element("<pre>");
outer.append(pre);
var wait = null;
var ws = null;
function connect() {
pre.empty();
var url = "";
var pod = scope.pod();
if (pod.metadata)
url += pod.metadata.selfLink;
else
url += pod;
url += "/log";
if (url.indexOf('?') === -1)
url += '?';
url += "follow=1";
var container = scope.container ? scope.container() : null;
if (container)
url += "&container=" + encodeURIComponent(container);
var writing = [];
var count = 0;
function drain() {
wait = null;
var at_bottom = pre[0].scrollHeight - pre[0].scrollTop <= pre[0].offsetHeight;
var text = writing.join("");
/*
* Stay under the limit. I wish we could use some other mechanism
* for limiting the log output, such as:
*
* https://github.com/kubernetes/kubernetes/issues/12447
*/
count += text.length;
var children, first, removed;
while (count > limit) {
children = pre.children();
if (children.length < 1)
break;
first = angular.element(children[0]);
removed = first.text().length;
first.remove();
count -= removed;
}
/* And add our text */
var span = angular.element("<span>").text(text);
writing.length = 0;
pre.append(span);
if (at_bottom)
pre[0].scrollTop = pre[0].scrollHeight;
}
ws = socket(url);
ws.onclose = function(ev) {
writing.push(ev.reason);
drain();
disconnect();
ws = null;
};
ws.onmessage = function(ev) {
writing.push(ev.data);
if (wait === null)
wait = window.setTimeout(drain, 50);
};
}
function disconnect() {
if (ws) {
ws.onopen = ws.onmessage = ws.onerror = ws.onclose = null;
if (ws.readyState < 2) // CLOSING
ws.close();
ws = null;
}
window.clearTimeout(wait);
wait = null;
}
scope.$watch("prevent", function(prevent) {
if (!prevent && !ws)
connect();
});
scope.$on("$destroy", disconnect);
}
};
}
])
/*
* Filter to display short docker ids
*
* {{ myid | kube-identifier }}
*
* Removes docker:// prefix and shortens.
*/
.filter('kubeIdentifier', function() {
var regex = /docker:\/\/([\w]{12})\w+/;
return function(item) {
var match = regex.exec(item);
if (match)
return match[1];
return item;
};
});
}());