cockpit/pkg/kubernetes/scripts/details.js

737 lines
28 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('object-describer/dist/object-describer.js');
require('kubernetes-object-describer/dist/object-describer.js');
require('angular-dialog.js');
require('./containers');
require('./date');
require('./kube-client');
require('./listing');
require('./utils');
require('./volumes');
require('../views/details-page.html');
require('../views/pod-container.html');
require('../views/details-page.html');
require('../views/item-delete.html');
require('../views/route-modify.html');
require('../views/replicationcontroller-modify.html');
require('../views/service-modify.html');
require('../views/deploymentconfig-body.html');
require('../views/replicationcontroller-pods.html');
require('../views/replicationcontroller-body.html');
require('../views/route-body.html');
require('../views/service-body.html');
require('../views/service-endpoint.html');
require('../views/pod-page.html');
require('../views/image-page.html');
require('../views/registry-dashboard-page.html');
require('../views/details-page.html');
require('../views/project-page.html');
require('../views/topology-page.html');
require('../views/node-page.html');
require('../views/dashboard-page.html');
require('../views/nodes-page.html');
require('../views/deploymentconfig-page.html');
require('../views/pv-page.html');
require('../views/container-page.html');
require('../views/service-page.html');
require('../views/group-page.html');
require('../views/containers-page.html');
require('../views/projects-page.html');
require('../views/user-page.html');
require('../views/images-page.html');
require('../views/replicationcontroller-page.html');
require('../views/route-page.html');
require('../views/imagestream-page.html');
require('../views/volumes-page.html');
function validItem(item, type) {
var valid = (item && (!type || item.kind === type) &&
item.spec && item.metadata);
var type_name = type || "Object";
if (!valid)
console.warn("Invalid " + type_name, item);
return valid;
}
function format_addresses_with_ports(addresses, ports) {
var text = addresses.join(", ");
if (ports && ports.length) {
text = text + ":" + ports.map(function(p) {
if (p.protocol === "TCP")
return p.port;
else
return p.port + "/" + p.protocol;
}).join(", ");
}
return text;
}
angular.module('kubernetes.details', [
'ngRoute',
'ui.cockpit',
'kubernetesUI',
'kubeClient',
'kubeUtils',
'kubernetes.listing',
'kubernetes.date',
'kubernetes.volumes',
'kubernetes.containers',
])
.config([
'$routeProvider',
function($routeProvider) {
$routeProvider
.when('/list/:namespace?', {
templateUrl: 'views/details-page.html',
controller: 'DetailsCtrl'
})
.when('/l/pods/:pod_namespace/:pod_name/:container_name', {
templateUrl: 'views/pod-container.html',
controller: 'ContainerCtrl',
})
.when('/l/:target_type/:target_namespace/:target', {
templateUrl: function (params) {
var re = /s$/;
var kind = params.target_type || "";
return 'views/' + kind.replace(re, "") + "-page.html";
},
controller: 'DetailCtrl',
resolve: {
'kindData': [
'kindData',
function (kindData) {
return kindData();
}
]
}
})
.when('/l/:target_type', {
templateUrl: 'views/details-page.html',
controller: 'DetailCtrl',
resolve: {
'kindData': [
'kindData',
function (kindData) {
return kindData();
}
]
}
});
}
])
.factory("kindData", [
"$q",
"$route",
"$location",
function($q, $route, $location) {
var typesToKinds = {
'services': 'Service',
'routes': 'Route',
'deploymentconfigs': 'DeploymentConfig',
'replicationcontrollers': 'ReplicationController',
'pods': 'Pod',
};
return function() {
var current = $route.current.params['target_type'];
var kind;
if (current)
kind = typesToKinds[current];
if (!kind) {
$location.path('/');
return $q.reject();
}
return $q.when({
'kind' : kind,
'type' : current,
});
};
}
])
.factory("detailsWatch", [
"kubeLoader",
"KubeDiscoverSettings",
function (loader, settings) {
return function(until) {
loader.watch("Pod", until);
loader.watch("Service", until);
loader.watch("ReplicationController", until);
loader.watch("Endpoints", until);
loader.watch("PersistentVolumeClaim", until);
settings().then(function(settings) {
if (settings.flavor === "openshift") {
loader.watch("DeploymentConfig", until);
loader.watch("Route", until);
}
});
};
}
])
.factory("detailsData", [
'kubeSelect',
'volumeData',
'KubeContainers',
"KubeTranslate",
function (select, volumeData, containers, translate) {
const _ = translate.gettext;
var names = {
'services': {
'name' : _("Services")
},
'routes': {
'name' : _("Routes"),
'flavor': "openshift"
},
'deploymentconfigs': {
'name': _("Deployment Configs"),
'flavor': "openshift"
},
'replicationcontrollers': {
'name' : _("Replication Controllers")
},
'pods': {
'name' : _("Pods")
}
};
function item_identifier(item) {
var meta = item.metadata || { };
var id = item.kind.toLowerCase() + "s/";
if (meta.namespace)
id = id + meta.namespace + "/";
return id + meta.name;
}
function service_endpoint(service) {
return select().kind("Endpoints")
.namespace(service.metadata.namespace)
.name(service.metadata.name)
.one();
}
function replicationcontroller_pods(item) {
var meta = item.metadata || {};
var spec = item.spec || {};
return select().kind("Pod")
.namespace(meta.namespace || "")
.label(spec.selector || {});
}
function podStatus(item) {
var status = item.status || {};
var meta = item.metadata || {};
if (meta.deletionTimestamp)
return "Terminating";
else
return status.phase;
}
return {
itemIdentifier: item_identifier,
serviceEndpoint: service_endpoint,
replicationcontrollerPods: replicationcontroller_pods,
podStatus: podStatus,
volumesForPod: volumeData.volumesForPod,
claimFromVolumeSource: volumeData.claimFromVolumeSource,
containers: containers,
names: names
};
}
])
/*
* The controller for the details view.
*/
.controller('DetailsCtrl', [
'$scope',
'kubeLoader',
'kubeSelect',
'KubeDiscoverSettings',
'ListingState',
'$location',
'itemActions',
'detailsData',
'detailsWatch',
function($scope, loader, select, discoverSettings, ListingState,
$location, actions, detailsData, detailsWatch) {
loader.listen(function() {
$scope.pods = select().kind("Pod");
$scope.services = select().kind("Service");
$scope.replicationcontrollers = select().kind("ReplicationController");
$scope.deploymentconfigs = select().kind("DeploymentConfig");
$scope.routes = select().kind("Route");
}, $scope);
detailsWatch($scope);
$scope.listing = new ListingState($scope);
$scope.showAll = true;
$scope.$on("activate", function(ev, id) {
ev.preventDefault();
actions.navigate(id);
});
/* All the data and actions available on the $scope */
angular.extend($scope, detailsData);
angular.extend($scope, actions);
}
])
.controller('DetailCtrl', [
'$scope',
'kindData',
'kubeLoader',
'kubeSelect',
'ListingState',
'$routeParams',
'$location',
'itemActions',
'detailsData',
'detailsWatch',
function($scope, kindData, loader, select, ListingState, $routeParams,
$location, actions, detailsData, detailsWatch) {
var target = $routeParams["target"] || "";
$scope.target = target;
$scope.name = detailsData.names[kindData.type].name;
loader.listen(function() {
if (kindData.type)
$scope[kindData.type] = select().kind(kindData.kind);
if (target && $routeParams.target_namespace) {
$scope.item = select().kind(kindData.kind)
.namespace($routeParams.target_namespace)
.name(target)
.one();
}
}, $scope);
detailsWatch($scope);
$scope.listing = new ListingState($scope);
$scope.listing.inline = true;
$scope.$on("activate", function(ev, id) {
ev.preventDefault();
actions.navigate(id);
});
/* All the data and actions available on the $scope */
angular.extend($scope, detailsData);
angular.extend($scope, actions);
angular.extend($scope, kindData);
}
])
.directive('kubernetesServiceCluster', function() {
return {
restrict: 'E',
link: function($scope, element, attributes) {
$scope.$watchGroup(["item.spec.clusterIP",
"item.spec.ports"], function(values) {
var text = format_addresses_with_ports([values[0]],
values[1]);
element.text(text);
});
}
};
})
.factory('itemActions', [
'$modal',
'$location',
function($modal, $location) {
function deleteItem(item) {
return $modal.open({
animation: false,
controller: 'ItemDeleteCtrl',
templateUrl: 'views/item-delete.html',
resolve: {
dialogData: function() {
return { item: item };
}
},
}).result;
}
function modifyRoute(item) {
return $modal.open({
animation: false,
controller: 'RouteModifyCtrl',
templateUrl: 'views/route-modify.html',
resolve: {
dialogData: function() {
return { item: item };
}
},
}).result;
}
function modifyRC(item) {
return $modal.open({
animation: false,
controller: 'RCModifyCtrl',
templateUrl: 'views/replicationcontroller-modify.html',
resolve: {
dialogData: function() {
return { item: item };
}
},
}).result;
}
function modifyService(item) {
return $modal.open({
animation: false,
controller: 'ServiceModifyCtrl',
templateUrl: 'views/service-modify.html',
resolve: {
dialogData: function() {
return { item: item };
}
},
}).result;
}
function navigate(path) {
var prefix = '/l';
path = path || "";
if (!path)
prefix = "/list";
if (path && path.indexOf('/') !== 0)
prefix = prefix + '/';
$location.path(prefix + path);
}
return {
modifyRC: modifyRC,
modifyRoute: modifyRoute,
deleteItem: deleteItem,
modifyService: modifyService,
navigate: navigate,
};
}
])
.controller("ItemDeleteCtrl", [
"$scope",
"$modalInstance",
"dialogData",
"kubeMethods",
function($scope, $instance, dialogData, methods) {
angular.extend($scope, dialogData);
$scope.performDelete = function performDelete() {
return methods.delete($scope.item).catch(function(ex) {
/* HACK: While debugging delete issues */
console.log(JSON.stringify(ex));
});
};
}
])
.controller("RCModifyCtrl", [
"$q",
"$scope",
"$modalInstance",
"dialogData",
"kubeMethods",
"KubeTranslate",
function($q, $scope, $instance, dialogData, methods, translate) {
const _ = translate.gettext;
var item = dialogData.item;
var fields = {};
if (!validItem(item, "ReplicationController")) {
$scope.$applyAsync(function () {
$scope.$dismiss();
});
return;
}
fields.replicas = item.spec ? item.spec.replicas : 1;
function validate() {
var defer = $q.defer();
var replicas = Number(fields.replicas.trim());
var ex;
if (isNaN(replicas) || replicas < 0)
ex = new Error(_("Not a valid number of replicas"));
else if (replicas > 128)
ex = new Error(_("The maximum number of replicas is 128"));
if (ex) {
ex.target = "#replicas";
defer.reject(ex);
} else {
defer.resolve({ spec: { replicas: replicas } });
}
return defer.promise;
}
$scope.fields = fields;
$scope.item = item;
$scope.performModify = function performModify() {
return validate().then(function(data) {
return methods.patch($scope.item, data);
});
};
}
])
.controller("RouteModifyCtrl", [
"$q",
"$scope",
"$modalInstance",
"dialogData",
"kubeMethods",
"KubeTranslate",
function($q, $scope, $instance, dialogData, methods, translate) {
const _ = translate.gettext;
var fields = {};
if (!validItem(dialogData.item, "Route")) {
$scope.$applyAsync(function () {
$scope.$dismiss();
});
return;
}
fields.host = dialogData.item.spec.host;
function validate() {
var defer = $q.defer();
var host = fields.host.trim();
var ex;
if (!host) {
ex = new Error(_("Not a valid value for Host"));
ex.target = "#host-value";
defer.reject(ex);
} else {
defer.resolve({ spec: { host: fields.host.trim() } });
}
return defer.promise;
}
$scope.fields = fields;
angular.extend($scope, dialogData);
$scope.performModify = function performModify() {
return validate().then(function(data) {
return methods.patch($scope.item, data);
});
};
}
])
.controller("ServiceModifyCtrl", [
"$q",
"$scope",
"$modalInstance",
"dialogData",
'kubeLoader',
'kubeSelect',
"KubeRequest",
"KubeTranslate",
"KubeFormat",
function($q, $scope, $instance, dialogData, loader, select, KubeRequest, translate, format) {
const _ = translate.gettext;
var fields = {};
var key;
if (!validItem(dialogData.item, "Service")) {
$scope.$applyAsync(function () {
$scope.$dismiss();
});
return;
}
$scope.rcs = select().kind("ReplicationController")
.namespace(dialogData.item.metadata.namespace || "")
.label(dialogData.item.spec.selector || {});
for (key in $scope.rcs) {
var item = $scope.rcs[key];
fields[key] = {
name: item.metadata.name,
replicas: item.spec.replicas,
};
}
$scope.service = dialogData.item;
$scope.fields = fields;
angular.extend($scope, dialogData);
function validate() {
var defer = $q.defer();
var link;
var objects = [];
var failures = [];
for (link in fields) {
var ex;
var replicas = Number(fields[link].replicas);
var name = fields[link].name;
if (isNaN(replicas) || replicas < 0)
ex = new Error(_("Not a valid number of replicas"));
else if (replicas > 128)
ex = new Error(_("The maximum number of replicas is 128"));
if (ex) {
ex.target = "#" + name;
failures.push(ex);
} else {
objects.push({
link: link,
name: name,
data: { spec: { replicas: replicas } }
});
}
}
if (failures.length > 0) {
defer.reject(failures);
} else
defer.resolve(objects);
return defer.promise;
}
$scope.performModify = function performModify() {
var defer = $q.defer();
var req;
validate().then(function (objects) {
function step() {
var obj = objects.shift();
if (!obj) {
defer.resolve();
return;
}
defer.notify(format.format(_("Updating $0..."), obj.name));
var config = { headers: { "Content-Type": "application/strategic-merge-patch+json" } };
new KubeRequest("PATCH", obj.link, JSON.stringify(obj.data), config)
.then(function(response) {
step();
}, function(response) {
var resp = response.data;
return defer.reject(resp || response);
});
}
step();
})
.catch(function(exs) {
defer.reject(exs);
});
var promise = defer.promise;
promise.cancel = function cancel() {
if (req && req.cancel)
req.cancel();
};
return promise;
};
}
])
.directive('deploymentconfigBody',
function() {
return {
restrict: 'A',
templateUrl: 'views/deploymentconfig-body.html'
};
}
)
.directive('replicationcontrollerPods',
function() {
return {
restrict: 'A',
templateUrl: 'views/replicationcontroller-pods.html'
};
}
)
.directive('replicationcontrollerBody',
function() {
return {
restrict: 'A',
templateUrl: 'views/replicationcontroller-body.html'
};
}
)
.directive('routeBody',
function() {
return {
restrict: 'A',
templateUrl: 'views/route-body.html'
};
}
)
.directive('serviceBody',
function() {
return {
restrict: 'A',
templateUrl: 'views/service-body.html'
};
}
)
.directive('serviceEndpoint',
function() {
return {
restrict: 'A',
templateUrl: 'views/service-endpoint.html'
};
}
);
}());