737 lines
28 KiB
JavaScript
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'
|
|
};
|
|
}
|
|
);
|
|
}());
|