356 lines
14 KiB
JavaScript
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;
|
|
};
|
|
}]);
|
|
}());
|