cockpit/pkg/kubernetes/scripts/app.js

356 lines
14 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-bootstrap-npm/dist/angular-bootstrap.js');
require('./kube-client');
require('./kube-client-cockpit');
require('./connection');
require('../views/auth-dialog.html');
require('../views/filter-bar.html');
require('../views/filter-project.html');
angular.module('kubernetes.app', [
'ui.bootstrap',
'kubeClient',
'kubeClient.cockpit',
'kubernetes.connection'
])
.controller('MainCtrl', [
'$scope',
'$location',
'$rootScope',
'$timeout',
'$modal',
'kubeLoader',
'kubeSelect',
'KubeDiscoverSettings',
'filterService',
'connectionActions',
function($scope, $location, $rootScope, $timeout, $modal,
loader, select, discoverSettings, filter, connectionActions) {
$scope.settings = { };
/* Used to set detect which route is active */
$scope.viewActive = function(segment) {
var url = $location.url() || "/";
var parts = url.split('?')[0].split("/");
if (!segment && !parts[1])
return true;
if (segment === parts[1])
return true;
return false;
};
/* Used to build simple route URLs */
$scope.viewUrl = function(segment, forceQS) {
var namespace = loader.limits.namespace;
var path;
var parts = [];
if (angular.isArray(namespace))
namespace = null;
if (segment)
parts.push(segment);
else
forceQS = true;
if (!forceQS && namespace)
parts.push(namespace);
path = "/" + parts.map(encodeURIComponent).join("/");
if (namespace && forceQS)
return path + "?namespace=" + encodeURIComponent(namespace);
else
return path;
};
/* Used while debugging */
$scope.console = console;
/* Show the body when ready */
function visible() {
document.getElementsByTagName("body")[0].removeAttribute("hidden");
}
/* Show after some seconds whether ready or not */
$timeout(visible, 3000);
/* Curtains related logic */
function connect(force) {
$scope.curtains = { };
discoverSettings(force).then(function(settings) {
$scope.settings = settings;
$scope.curtains = null;
filter.globals(settings.isAdmin);
filter.load().then(visible);
}, function(resp) {
$scope.curtains = {
status: resp.status,
resp: resp,
message: resp.message || resp.statusText,
};
$scope.settings = null;
visible();
});
}
/* Connect automatically initially */
connect();
/* Used by reconnect buttons */
$scope.reconnect = function(force) {
if (force === undefined)
force = true;
discoverSettings(force);
loader.reset();
connect();
};
/* When the loader changes digest */
loader.listen(function() {
$rootScope.$applyAsync();
}, $rootScope);
$scope.changeAuth = function(ex) {
var promise = $modal.open({
animation: false,
controller: 'ChangeAuthCtrl',
templateUrl: 'views/auth-dialog.html',
resolve: {
dialogData: function() {
return connectionActions.load(ex);
}
},
}).result;
/* If the change is successful, reconnect */
promise.then(function(force) {
$scope.reconnect(force);
});
return promise;
};
}
])
.directive('filterBar', [
'filterService',
function(filter) {
return {
restrict: 'E',
scope: true,
transclude: true,
link: function(scope, element, attrs) {
scope.filter = filter;
},
templateUrl: 'views/filter-bar.html'
};
}
])
.directive('filterProject', [
'filterService',
function(filter) {
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs) {
scope.filter = filter;
},
templateUrl: 'views/filter-project.html'
};
}
])
.factory('filterService', [
'$q',
'$route',
'$rootScope',
'kubeLoader',
'kubeSelect',
'KubeDiscoverSettings',
function($q, $route, $rootScope, loader, select, discoverSettings) {
/*
* We have the following cases to account for:
*
* Openshift:
* - Have Project objects
* - Project objects are listable by any user, only accessilbe returned
*
* Kubernetes and Openshift
* - Namespace objects are only accessible to all users
*
* The globals variable is set based on this.
*/
var globals = true;
var promise = discoverSettings().then(function(settings) {
var ret = [];
if (settings.flavor === "openshift") {
ret.push(loader.watch("projects", $rootScope));
ret.push(loader.load("projects"));
}
if (settings.isAdmin)
ret.push(loader.watch("namespaces", $rootScope));
return $q.all(ret);
});
/*
* When either a Namespace or Project is loaded we'll want to reinterpret
* how we look at the current namespace. This helps to handle cases where
* the user can't see all projects, and one is loaded.
*/
loader.listen(function(present) {
var link, object;
for (link in present) {
object = present[link];
if (object.kind == "Namespace" || object.kind == "Project") {
loadNamespace($route.current);
return;
}
}
});
function calcAvailable() {
var all;
if (globals)
all = select().kind("Namespace");
if (!all || all.length === 0)
all = select().kind("Project")
.statusPhase("Active");
var link, meta;
var ret = [];
for (link in all) {
meta = all[link].metadata || { };
if (meta.name)
ret.push(meta.name);
}
return ret;
}
function loadNamespace(route) {
var value = null;
if (route)
value = route.params["namespace"] || null;
/*
* When we can't see globals, we tell the loader about
* all namespaces that we can see. It'll open up individual
* watches about those namespaces.
*/
if (value === null && !globals) {
value = calcAvailable();
/* We might not be global, but the projects may not
* be loaded yet */
if (value.length < 1)
value = null;
}
if (!angular.equals(value, loader.limits.namespaces)) {
loader.limit({ namespace: value });
}
}
$rootScope.$on("$routeChangeSuccess", function (event, current, prev) {
loadNamespace(current);
});
$rootScope.$on("$routeUpdate", function (event, current, prev) {
loadNamespace(current);
});
if ($route.current)
loadNamespace($route.current);
return {
load: function() {
return promise;
},
globals: function(value) {
if (arguments.length === 0)
return globals;
value = !!value;
if (globals !== value) {
globals = value;
loadNamespace($route.current);
}
},
namespaces: calcAvailable,
namespace: function(value) {
if (arguments.length === 0)
return $route.current.params["namespace"];
var copy = angular.copy($route.current.params);
copy["namespace"] = value || undefined;
copy["target"] = null;
$route.updateParams(copy);
}
};
}
])
/* The default orderBy filter doesn't work on objects */
.filter('orderObjectBy', function() {
return function(items, field) {
var i;
var sorted = [];
for (i in items)
sorted.push(items[i]);
if (!angular.isArray(field))
field = [ String(field) ];
var criteria = field.map(function(v) {
return v.split('.');
});
function value(obj, x) {
return obj ? obj[x] : undefined;
}
sorted.sort(function(a, b) {
var ra, rb, i;
var len = criteria.length;
for (i = 0; i < len; i++) {
ra = criteria[i].reduce(value, a);
rb = criteria[i].reduce(value, b);
if (ra === rb)
continue;
return (ra > rb ? 1 : -1);
}
return 0;
});
return sorted;
};
})
.filter("formatBytes", [
"KubeFormat",
function(format) {
return function(num) {
if (typeof num == "number")
return format.formatBytes(num);
return num;
};
}]);
}());