diff --git a/app/index.html b/app/index.html
index 1a08e8c883..403207e4ca 100644
--- a/app/index.html
+++ b/app/index.html
@@ -154,6 +154,7 @@
JavaScript Required
+
@@ -164,25 +165,16 @@ JavaScript Required
-
-
-
-
-
-
-
-
-
-
+
@@ -193,7 +185,6 @@ JavaScript Required
-
diff --git a/app/scripts/app.js b/app/scripts/app.js
index 1aadb680ac..a9adf1a29b 100644
--- a/app/scripts/app.js
+++ b/app/scripts/app.js
@@ -29,7 +29,8 @@ angular
'ui.select',
'angular-inview',
'angularMoment',
- 'ab-base64'
+ 'ab-base64',
+ 'openshiftCommon'
])
.config(function ($routeProvider) {
$routeProvider
@@ -417,9 +418,6 @@ angular
redirectTo: '/'
});
})
- .constant("API_CFG", _.get(window.OPENSHIFT_CONFIG, "api", {}))
- .constant("APIS_CFG", _.get(window.OPENSHIFT_CONFIG, "apis", {}))
- .constant("AUTH_CFG", _.get(window.OPENSHIFT_CONFIG, "auth", {}))
.constant("LOGGING_URL", _.get(window.OPENSHIFT_CONFIG, "loggingURL"))
.constant("METRICS_URL", _.get(window.OPENSHIFT_CONFIG, "metricsURL"))
.constant("LIMIT_REQUEST_OVERRIDES", _.get(window.OPENSHIFT_CONFIG, "limitRequestOverrides"))
@@ -462,18 +460,7 @@ angular
// See http://momentjs.com/docs/#/displaying/format/
titleFormat: 'LLL'
})
- .config(function($httpProvider, AuthServiceProvider, RedirectLoginServiceProvider, AUTH_CFG, API_CFG, kubernetesContainerSocketProvider) {
- $httpProvider.interceptors.push('AuthInterceptor');
-
- AuthServiceProvider.LoginService('RedirectLoginService');
- AuthServiceProvider.LogoutService('DeleteTokenLogoutService');
- // TODO: fall back to cookie store when localStorage is unavailable (see known issues at http://caniuse.com/#feat=namevalue-storage)
- AuthServiceProvider.UserStore('LocalStorageUserStore');
-
- RedirectLoginServiceProvider.OAuthClientID(AUTH_CFG.oauth_client_id);
- RedirectLoginServiceProvider.OAuthAuthorizeURI(AUTH_CFG.oauth_authorize_uri);
- RedirectLoginServiceProvider.OAuthRedirectURI(URI(AUTH_CFG.oauth_redirect_base).segment("oauth").toString());
-
+ .config(function(kubernetesContainerSocketProvider) {
// Configure the container terminal
kubernetesContainerSocketProvider.WebSocketFactory = "ContainerWebSocket";
})
@@ -515,119 +502,3 @@ angular
});
hawtioPluginLoader.addModule('openshiftConsole');
-
-// API Discovery, this runs before the angular app is bootstrapped
-// TODO we want this to be possible with a single request against the API instead of being dependent on the numbers of groups and versions
-hawtioPluginLoader.registerPreBootstrapTask(function(next) {
- // Skips api discovery, needed to run spec tests
- if ( _.get(window, "OPENSHIFT_CONFIG.api.k8s.resources") ) {
- next();
- return;
- }
-
- var api = {
- k8s: {},
- openshift: {}
- };
- var apis = {};
- var API_DISCOVERY_ERRORS = [];
- var protocol = window.location.protocol + "//";
-
- // Fetch /api/v1 for legacy k8s resources, we will never bump the version of these legacy apis so fetch version immediately
- var k8sBaseURL = protocol + window.OPENSHIFT_CONFIG.api.k8s.hostPort + window.OPENSHIFT_CONFIG.api.k8s.prefix;
- var k8sDeferred = $.get(k8sBaseURL + "/v1")
- .done(function(data) {
- api.k8s.v1 = _.indexBy(data.resources, 'name');
- })
- .fail(function(data, textStatus, jqXHR) {
- API_DISCOVERY_ERRORS.push({
- data: data,
- textStatus: textStatus,
- xhr: jqXHR
- });
- });
-
- // Fetch /oapi/v1 for legacy openshift resources, we will never bump the version of these legacy apis so fetch version immediately
- var osBaseURL = protocol + window.OPENSHIFT_CONFIG.api.openshift.hostPort + window.OPENSHIFT_CONFIG.api.openshift.prefix;
- var osDeferred = $.get(osBaseURL + "/v1")
- .done(function(data) {
- api.openshift.v1 = _.indexBy(data.resources, 'name');
- })
- .fail(function(data, textStatus, jqXHR) {
- API_DISCOVERY_ERRORS.push({
- data: data,
- textStatus: textStatus,
- xhr: jqXHR
- });
- });
-
- // Fetch /apis to get the list of groups and versions, then fetch each group/
- // Because the api discovery doc returns arrays and we want maps, this creates a structure like:
- // {
- // extensions: {
- // name: "extensions",
- // preferredVersion: "v1beta1",
- // versions: {
- // v1beta1: {
- // version: "v1beta1",
- // groupVersion: "extensions/v1beta1"
- // resources: {
- // daemonsets: {
- // /* resource returned from discovery API */
- // }
- // }
- // }
- // }
- // }
- // }
- var apisBaseURL = protocol + window.OPENSHIFT_CONFIG.apis.hostPort + window.OPENSHIFT_CONFIG.apis.prefix;
- var apisDeferred = $.get(apisBaseURL)
- .then(function(data) {
- var apisDeferredVersions = [];
- _.each(data.groups, function(apiGroup) {
- var group = {
- name: apiGroup.name,
- preferredVersion: apiGroup.preferredVersion.version,
- versions: {}
- };
- apis[group.name] = group;
- _.each(apiGroup.versions, function(apiVersion) {
- var versionStr = apiVersion.version;
- group.versions[versionStr] = {
- version: versionStr,
- groupVersion: apiVersion.groupVersion
- };
- apisDeferredVersions.push($.get(apisBaseURL + "/" + apiVersion.groupVersion)
- .done(function(data) {
- group.versions[versionStr].resources = _.indexBy(data.resources, 'name');
- })
- .fail(function(data, textStatus, jqXHR) {
- API_DISCOVERY_ERRORS.push({
- data: data,
- textStatus: textStatus,
- xhr: jqXHR
- });
- }));
- });
- });
- return $.when.apply(this, apisDeferredVersions);
- }, function(data, textStatus, jqXHR) {
- API_DISCOVERY_ERRORS.push({
- data: data,
- textStatus: textStatus,
- xhr: jqXHR
- });
- });
-
- // Will be called on success or failure
- var discoveryFinished = function() {
- window.OPENSHIFT_CONFIG.api.k8s.resources = api.k8s;
- window.OPENSHIFT_CONFIG.api.openshift.resources = api.openshift;
- window.OPENSHIFT_CONFIG.apis.groups = apis;
- if (API_DISCOVERY_ERRORS.length) {
- window.OPENSHIFT_CONFIG.apis.API_DISCOVERY_ERRORS = API_DISCOVERY_ERRORS;
- }
- next();
- };
- $.when(k8sDeferred,osDeferred,apisDeferred).always(discoveryFinished);
-});
diff --git a/app/scripts/services/api.js b/app/scripts/services/api.js
deleted file mode 100644
index dbd2a0527f..0000000000
--- a/app/scripts/services/api.js
+++ /dev/null
@@ -1,338 +0,0 @@
-'use strict';
-
-// ResourceGroupVersion represents a fully qualified resource
-function ResourceGroupVersion(resource, group, version) {
- this.resource = resource;
- this.group = group;
- this.version = version;
- return this;
-}
-// toString() includes the group and version information if present
-ResourceGroupVersion.prototype.toString = function() {
- var s = this.resource;
- if (this.group) { s += "/" + this.group; }
- if (this.version) { s += "/" + this.version; }
- return s;
-};
-// primaryResource() returns the resource with any subresources removed
-ResourceGroupVersion.prototype.primaryResource = function() {
- if (!this.resource) { return ""; }
- var i = this.resource.indexOf('/');
- if (i === -1) { return this.resource; }
- return this.resource.substring(0,i);
-};
-// subresources() returns a (possibly empty) list of subresource segments
-ResourceGroupVersion.prototype.subresources = function() {
- var segments = (this.resource || '').split("/");
- segments.shift();
- return segments;
-};
-// equals() returns true if the given resource, group, and version match.
-// If omitted, group and version are not compared.
-ResourceGroupVersion.prototype.equals = function(resource, group, version) {
- if (this.resource !== resource) { return false; }
- if (arguments.length === 1) { return true; }
- if (this.group !== group) { return false; }
- if (arguments.length === 2) { return true; }
- if (this.version !== version) { return false; }
- return true;
-};
-
-
-angular.module('openshiftConsole')
-.factory('APIService', function(API_CFG,
- APIS_CFG,
- AuthService,
- Constants,
- Logger,
- $q,
- $http,
- $filter,
- $window) {
- // Set the default api versions the console will use if otherwise unspecified
- var defaultVersion = {
- "": "v1",
- "extensions": "v1beta1"
- };
-
- // toResourceGroupVersion() returns a ResourceGroupVersion.
- // If resource is already a ResourceGroupVersion, returns itself.
- //
- // if r is a string, the empty group and default version for the empty group are assumed.
- //
- // if r is an object, the resource, group, and version attributes are read.
- // a missing group attribute defaults to the legacy group.
- // a missing version attribute defaults to the default version for the group, or undefined if the group is unknown.
- //
- // if r is already a ResourceGroupVersion, it is returned as-is
- var toResourceGroupVersion = function(r) {
- if (r instanceof ResourceGroupVersion) {
- return r;
- }
- var resource, group, version;
- if (angular.isString(r)) {
- resource = normalizeResource(r);
- group = '';
- version = defaultVersion[group];
- } else if (r && r.resource) {
- resource = normalizeResource(r.resource);
- group = r.group || '';
- version = r.version || defaultVersion[group] || _.get(APIS_CFG, ["groups", group, "preferredVersion"]);
- }
- return new ResourceGroupVersion(resource, group, version);
- };
-
- // normalizeResource lowercases the first segment of the given resource. subresources can be case-sensitive.
- function normalizeResource(resource) {
- if (!resource) {
- return resource;
- }
- var i = resource.indexOf('/');
- if (i === -1) {
- return resource.toLowerCase();
- }
- return resource.substring(0, i).toLowerCase() + resource.substring(i);
- }
-
- // port of group_version.go#ParseGroupVersion
- var parseGroupVersion = function(apiVersion) {
- if (!apiVersion) {
- return undefined;
- }
- var parts = apiVersion.split("/");
- if (parts.length === 1) {
- if (parts[0] === "v1") {
- return {group: '', version: parts[0]};
- }
- return {group: parts[0], version: ''};
- }
- if (parts.length === 2) {
- return {group:parts[0], version: parts[1]};
- }
- Logger.warn('Invalid apiVersion "' + apiVersion + '"');
- return undefined;
- };
-
- var objectToResourceGroupVersion = function(apiObject) {
- if (!apiObject || !apiObject.kind || !apiObject.apiVersion) {
- return undefined;
- }
- var resource = kindToResource(apiObject.kind);
- if (!resource) {
- return undefined;
- }
- var groupVersion = parseGroupVersion(apiObject.apiVersion);
- if (!groupVersion) {
- return undefined;
- }
- return new ResourceGroupVersion(resource, groupVersion.group, groupVersion.version);
- };
-
- // deriveTargetResource figures out the fully qualified destination to submit the object to.
- // if resource is a string, and the object's kind matches the resource, the object's group/version are used.
- // if resource is a ResourceGroupVersion, and the object's kind and group match, the object's version is used.
- // otherwise, resource is used as-is.
- var deriveTargetResource = function(resource, object) {
- if (!resource || !object) {
- return undefined;
- }
- var objectResource = kindToResource(object.kind);
- var objectGroupVersion = parseGroupVersion(object.apiVersion);
- var resourceGroupVersion = toResourceGroupVersion(resource);
- if (!objectResource || !objectGroupVersion || !resourceGroupVersion) {
- return undefined;
- }
-
- // We specified something like "pods"
- if (angular.isString(resource)) {
- // If the object had a matching kind {"kind":"Pod","apiVersion":"v1"}, use the group/version from the object
- if (resourceGroupVersion.equals(objectResource)) {
- resourceGroupVersion.group = objectGroupVersion.group;
- resourceGroupVersion.version = objectGroupVersion.version;
- }
- return resourceGroupVersion;
- }
-
- // If the resource was already a fully specified object,
- // require the group to match as well before taking the version from the object
- if (resourceGroupVersion.equals(objectResource, objectGroupVersion.group)) {
- resourceGroupVersion.version = objectGroupVersion.version;
- }
- return resourceGroupVersion;
- };
-
- // port of restmapper.go#kindToResource
- // humanize will add spaces between words in the resource
- function kindToResource(kind, humanize) {
- if (!kind) {
- return "";
- }
- var resource = kind;
- if (humanize) {
- var humanizeKind = $filter("humanizeKind");
- resource = humanizeKind(resource);
- }
- resource = String(resource).toLowerCase();
- if (resource === 'endpoints' || resource === 'securitycontextconstraints') {
- // no-op, plural is the singular
- }
- else if (resource[resource.length-1] === 's') {
- resource = resource + 'es';
- }
- else if (resource[resource.length-1] === 'y') {
- resource = resource.substring(0, resource.length-1) + 'ies';
- }
- else {
- resource = resource + 's';
- }
-
- return resource;
- }
-
- // apiInfo returns the host/port, prefix, group, and version for the given resource,
- // or undefined if the specified resource/group/version is known not to exist.
- var apiInfo = function(resource) {
- // If API discovery had any failures, calls to api info should redirect to the error page
- if (APIS_CFG.API_DISCOVERY_ERRORS) {
- var possibleCertFailure = _.every(APIS_CFG.API_DISCOVERY_ERRORS, function(error){
- return _.get(error, "data.status") === 0;
- });
- if (possibleCertFailure && !AuthService.isLoggedIn()) {
- // will trigger a login flow which will redirect to the api server
- AuthService.withUser();
- return;
- }
- // Otherwise go to the error page, the server might be down. Can't use Navigate.toErrorPage or it will create a circular dependency
- $window.location.href = URI('error').query({
- error_description: "Unable to load details about the server. If the problem continues, please contact your system administrator.",
- error: "API_DISCOVERY"
- }).toString();
- return;
- }
-
- resource = toResourceGroupVersion(resource);
- var primaryResource = resource.primaryResource();
-
- // API info for resources in an API group, if the resource was not found during discovery return undefined
- if (resource.group) {
- if (!_.get(APIS_CFG, ["groups", resource.group, "versions", resource.version, "resources", primaryResource])) {
- return undefined;
- }
- return {
- hostPort: APIS_CFG.hostPort,
- prefix: APIS_CFG.prefix,
- group: resource.group,
- version: resource.version
- };
- }
-
- // Resources without an API group could be legacy k8s or origin resources.
- // Scan through resources to determine which this is.
- var api;
- for (var apiName in API_CFG) {
- api = API_CFG[apiName];
- if (!_.get(api, ["resources", resource.version, primaryResource])) {
- continue;
- }
- return {
- hostPort: api.hostPort,
- prefix: api.prefix,
- version: resource.version
- };
- }
- return undefined;
- };
-
- var invalidObjectKindOrVersion = function(apiObject) {
- var kind = "";
- var version = "";
- if (apiObject && apiObject.kind) { kind = apiObject.kind; }
- if (apiObject && apiObject.apiVersion) { version = apiObject.apiVersion; }
- return "Invalid kind ("+kind+") or API version ("+version+")";
- };
- var unsupportedObjectKindOrVersion = function(apiObject) {
- var kind = "";
- var version = "";
- if (apiObject && apiObject.kind) { kind = apiObject.kind; }
- if (apiObject && apiObject.apiVersion) { version = apiObject.apiVersion; }
- return "The API version "+version+" for kind " + kind + " is not supported by this server";
- };
-
- // Returns an array of available kinds, including their group
- var calculateAvailableKinds = function(includeClusterScoped) {
- var kinds = [];
- var rejectedKinds = Constants.AVAILABLE_KINDS_BLACKLIST;
-
- // Legacy openshift and k8s kinds
- _.each(API_CFG, function(api) {
- _.each(api.resources.v1, function(resource) {
- if (resource.namespaced || includeClusterScoped) {
- // Exclude subresources and any rejected kinds
- if (resource.name.indexOf("/") >= 0 || _.contains(rejectedKinds, resource.kind)) {
- return;
- }
-
- kinds.push({
- kind: resource.kind
- });
- }
- });
- });
-
- // Kinds under api groups
- _.each(APIS_CFG.groups, function(group) {
- // Use the console's default version first, and the server's preferred version second
- var preferredVersion = defaultVersion[group.name] || group.preferredVersion;
- _.each(group.versions[preferredVersion].resources, function(resource) {
- // Exclude subresources and any rejected kinds
- if (resource.name.indexOf("/") >= 0 || _.contains(rejectedKinds, resource.kind)) {
- return;
- }
-
- // Exclude duplicate kinds we know about that map to the same storage as another group/kind
- // This is unusual, so we are special casing these
- if (group.name === "autoscaling" && resource.kind === "HorizontalPodAutoscaler" ||
- group.name === "batch" && resource.kind === "Job"
- ) {
- return;
- }
-
- if (resource.namespaced || includeClusterScoped) {
- kinds.push({
- kind: resource.kind,
- group: group.name
- });
- }
- });
- });
-
- return _.uniq(kinds, false, function(value) {
- return value.group + "/" + value.kind;
- });
- };
-
- var namespacedKinds = calculateAvailableKinds(false);
- var allKinds = calculateAvailableKinds(true);
-
- var availableKinds = function(includeClusterScoped) {
- return includeClusterScoped ? allKinds : namespacedKinds;
- };
-
- return {
- toResourceGroupVersion: toResourceGroupVersion,
-
- parseGroupVersion: parseGroupVersion,
-
- objectToResourceGroupVersion: objectToResourceGroupVersion,
-
- deriveTargetResource: deriveTargetResource,
-
- kindToResource: kindToResource,
-
- apiInfo: apiInfo,
-
- invalidObjectKindOrVersion: invalidObjectKindOrVersion,
- unsupportedObjectKindOrVersion: unsupportedObjectKindOrVersion,
- availableKinds: availableKinds
- };
-});
diff --git a/app/scripts/services/auth.js b/app/scripts/services/auth.js
deleted file mode 100644
index 58f3bae00c..0000000000
--- a/app/scripts/services/auth.js
+++ /dev/null
@@ -1,278 +0,0 @@
-'use strict';
-
-angular.module('openshiftConsole')
-// In a config step, set the desired user store and login service. For example:
-// AuthServiceProvider.setUserStore('LocalStorageUserStore')
-// AuthServiceProvider.setLoginService('RedirectLoginService')
-//
-// AuthService provides the following functions:
-// withUser()
-// returns a promise that resolves when there is a current user
-// starts a login if there is no current user
-// setUser(user, token[, ttl])
-// sets the current user and token to use for authenticated requests
-// if ttl is specified, it indicates how many seconds the user and token are valid
-// triggers onUserChanged callbacks if the new user is different than the current user
-// requestRequiresAuth(config)
-// returns true if the request is to a protected URL
-// addAuthToRequest(config)
-// adds auth info to the request, if available
-// if specified, uses config.auth.token as the token, otherwise uses the token store
-// startLogin()
-// returns a promise that is resolved when the login is complete
-// onLogin(callback)
-// the given callback is called whenever a login is completed
-// onUserChanged(callback)
-// the given callback is called whenever the current user changes
-.provider('AuthService', function() {
- var _userStore = "";
- this.UserStore = function(userStoreName) {
- if (userStoreName) {
- _userStore = userStoreName;
- }
- return _userStore;
- };
- var _loginService = "";
- this.LoginService = function(loginServiceName) {
- if (loginServiceName) {
- _loginService = loginServiceName;
- }
- return _loginService;
- };
- var _logoutService = "";
- this.LogoutService = function(logoutServiceName) {
- if (logoutServiceName) {
- _logoutService = logoutServiceName;
- }
- return _logoutService;
- };
-
- var loadService = function(injector, name, setter) {
- if (!name) {
- throw setter + " not set";
- } else if (angular.isString(name)) {
- return injector.get(name);
- } else {
- return injector.invoke(name);
- }
- };
-
- this.$get = function($q, $injector, $log, $rootScope, Logger) {
- var authLogger = Logger.get("auth");
- authLogger.log('AuthServiceProvider.$get', arguments);
-
- var _loginCallbacks = $.Callbacks();
- var _logoutCallbacks = $.Callbacks();
- var _userChangedCallbacks = $.Callbacks();
-
- var _loginPromise = null;
- var _logoutPromise = null;
-
- var userStore = loadService($injector, _userStore, "AuthServiceProvider.UserStore()");
- if (!userStore.available()) {
- Logger.error("AuthServiceProvider.$get user store " + _userStore + " not available");
- }
- var loginService = loadService($injector, _loginService, "AuthServiceProvider.LoginService()");
- var logoutService = loadService($injector, _logoutService, "AuthServiceProvider.LogoutService()");
-
- return {
-
- // Returns the configured user store
- UserStore: function() {
- return userStore;
- },
-
- // Returns true if currently logged in.
- isLoggedIn: function() {
- return !!userStore.getUser();
- },
-
- // Returns a promise of a user, which is resolved with a logged in user. Triggers a login if needed.
- withUser: function() {
- var user = userStore.getUser();
- if (user) {
- $rootScope.user = user;
- authLogger.log('AuthService.withUser()', user);
- return $q.when(user);
- } else {
- authLogger.log('AuthService.withUser(), calling startLogin()');
- return this.startLogin();
- }
- },
-
- setUser: function(user, token, ttl) {
- authLogger.log('AuthService.setUser()', user, token, ttl);
- var oldUser = userStore.getUser();
- userStore.setUser(user, ttl);
- userStore.setToken(token, ttl);
-
- $rootScope.user = user;
-
- var oldName = oldUser && oldUser.metadata && oldUser.metadata.name;
- var newName = user && user.metadata && user.metadata.name;
- if (oldName !== newName) {
- authLogger.log('AuthService.setUser(), user changed', oldUser, user);
- _userChangedCallbacks.fire(user);
- }
- },
-
- requestRequiresAuth: function(config) {
- var requiresAuth = !!config.auth;
- authLogger.log('AuthService.requestRequiresAuth()', config.url.toString(), requiresAuth);
- return requiresAuth;
- },
- addAuthToRequest: function(config) {
- // Use the given token, if provided
- var token = "";
- if (config && config.auth && config.auth.token) {
- token = config.auth.token;
- authLogger.log('AuthService.addAuthToRequest(), using token from request config', token);
- } else {
- token = userStore.getToken();
- authLogger.log('AuthService.addAuthToRequest(), using token from user store', token);
- }
- if (!token) {
- authLogger.log('AuthService.addAuthToRequest(), no token available');
- return false;
- }
-
- // Handle web socket requests with a parameter
- if (config.method === 'WATCH') {
- config.url = URI(config.url).addQuery({access_token: token}).toString();
- authLogger.log('AuthService.addAuthToRequest(), added token param', config.url);
- } else {
- config.headers["Authorization"] = "Bearer " + token;
- authLogger.log('AuthService.addAuthToRequest(), added token header', config.headers["Authorization"]);
- }
- return true;
- },
-
- startLogin: function() {
- if (_loginPromise) {
- authLogger.log("Login already in progress");
- return _loginPromise;
- }
- var self = this;
- _loginPromise = loginService.login().then(function(result) {
- self.setUser(result.user, result.token, result.ttl);
- _loginCallbacks.fire(result.user);
- }).catch(function(err) {
- Logger.error(err);
- }).finally(function() {
- _loginPromise = null;
- });
- return _loginPromise;
- },
-
- startLogout: function() {
- if (_logoutPromise) {
- authLogger.log("Logout already in progress");
- return _logoutPromise;
- }
- var self = this;
- var user = userStore.getUser();
- var token = userStore.getToken();
- var wasLoggedIn = this.isLoggedIn();
- _logoutPromise = logoutService.logout(user, token).then(function() {
- authLogger.log("Logout service success");
- }).catch(function(err) {
- authLogger.error("Logout service error", err);
- }).finally(function() {
- // Clear the user and token
- self.setUser(null, null);
- // Make sure isLoggedIn() returns false before we fire logout callbacks
- var isLoggedIn = self.isLoggedIn();
- // Only fire logout callbacks if we transitioned from a logged in state to a logged out state
- if (wasLoggedIn && !isLoggedIn) {
- _logoutCallbacks.fire();
- }
- _logoutPromise = null;
- });
- return _logoutPromise;
- },
-
- // TODO: add a way to unregister once we start doing in-page logins
- onLogin: function(callback) {
- _loginCallbacks.add(callback);
- },
- // TODO: add a way to unregister once we start doing in-page logouts
- onLogout: function(callback) {
- _logoutCallbacks.add(callback);
- },
- // TODO: add a way to unregister once we start doing in-page user changes
- onUserChanged: function(callback) {
- _userChangedCallbacks.add(callback);
- }
- };
- };
-})
-// register the interceptor as a service
-.factory('AuthInterceptor', ['$q', 'AuthService', function($q, AuthService) {
- var pendingRequestConfigs = [];
- // TODO: subscribe to user change events to empty the saved configs
- // TODO: subscribe to login events to retry the saved configs
-
- return {
- // If auth is not needed, or is already present, returns a config
- // If auth is needed and not present, starts a login flow and returns a promise of a config
- request: function(config) {
- // Requests that don't require auth can continue
- if (!AuthService.requestRequiresAuth(config)) {
- // console.log("No auth required", config.url);
- return config;
- }
-
- // If we could add auth info, we can continue
- if (AuthService.addAuthToRequest(config)) {
- // console.log("Auth added", config.url);
- return config;
- }
-
- // We should have added auth info, but couldn't
-
- // If we were specifically told not to trigger a login, return
- if (config.auth && config.auth.triggerLogin === false) {
- return config;
- }
-
- // 1. Set up a deferred and remember this config, so we can add auth info and resume once login is complete
- var deferred = $q.defer();
- pendingRequestConfigs.push([deferred, config, 'request']);
- // 2. Start the login flow
- AuthService.startLogin();
- // 3. Return the deferred's promise
- return deferred.promise;
- },
-
- responseError: function(rejection) {
- var authConfig = rejection.config.auth || {};
-
- // Requests that didn't require auth can continue
- if (!AuthService.requestRequiresAuth(rejection.config)) {
- // console.log("No auth required", rejection.config.url);
- return $q.reject(rejection);
- }
-
- // If we were specifically told not to trigger a login, return
- if (authConfig.triggerLogin === false) {
- return $q.reject(rejection);
- }
-
- // detect if this is an auth error (401) or other error we should trigger a login flow for
- var status = rejection.status;
- switch (status) {
- case 401:
- // console.log('responseError', status);
- // 1. Set up a deferred and remember this config, so we can add auth info and retry once login is complete
- var deferred = $q.defer();
- pendingRequestConfigs.push([deferred, rejection.config, 'responseError']);
- // 2. Start the login flow
- AuthService.startLogin();
- // 3. Return the deferred's promise
- return deferred.promise;
- default:
- return $q.reject(rejection);
- }
- }
- };
-}]);
diff --git a/app/scripts/services/authorization.js b/app/scripts/services/authorization.js
deleted file mode 100644
index 609d441083..0000000000
--- a/app/scripts/services/authorization.js
+++ /dev/null
@@ -1,148 +0,0 @@
-'use strict';
-
-angular.module("openshiftConsole")
- .factory("AuthorizationService", function($q, $cacheFactory, Logger, $interval, APIService, DataService){
-
- var currentProject = null;
- var cachedRulesByProject = $cacheFactory('rulesCache', {
- number: 10
- });
- // Permisive mode will cause no checks to be done for the user actions.
- var permissiveMode = false;
-
- var REVIEW_RESOURCES = ["localresourceaccessreviews", "localsubjectaccessreviews", "resourceaccessreviews", "selfsubjectrulesreviews", "subjectaccessreviews"];
-
- // Transform data from:
- // rules = {resources: ["jobs"], apiGroups: ["extensions"], verbs:["create","delete","get","list","update"]}
- // into:
- // normalizedRules = {"extensions": {"jobs": ["create","delete","get","list","update"]}}
- var normalizeRules = function(rules) {
- var normalizedRules = {};
- _.each(rules, function(rule) {
- _.each(rule.apiGroups, function(apiGroup) {
- if (!normalizedRules[apiGroup]) {
- normalizedRules[apiGroup] = {};
- }
- _.each(rule.resources, function(resource) {
- normalizedRules[apiGroup][resource] = rule.verbs;
- });
- });
- });
- return normalizedRules;
- };
-
- // Check if resource name meets one of following conditions, since those resources can't be create/update via `Add to project` page:
- // - 'projectrequests'
- // - subresource that contains '/', eg: 'builds/source', 'builds/logs', ...
- // - resource is in REVIEW_RESOURCES list
- var checkResource = function(resource) {
- if (resource === "projectrequests" || _.contains(resource, "/") || _.contains(REVIEW_RESOURCES, resource)) {
- return false;
- } else {
- return true;
- }
- };
-
- // Check if user can create/update any resource on the 'Add to project' so the button will be displayed.
- var canAddToProjectCheck = function(rules) {
- return _.some(rules, function(rule) {
- return _.some(rule.resources, function(resource) {
- return checkResource(resource) && !_.isEmpty(_.intersection(rule.verbs ,(["*", "create", "update"])));
- });
- });
- };
-
- // forceRefresh is a boolean to bust the cache & request new perms
- var getProjectRules = function(projectName, forceRefresh) {
- var deferred = $q.defer();
- currentProject = projectName;
- var projectRules = cachedRulesByProject.get(projectName);
- var rulesResource = "selfsubjectrulesreviews";
- if (!projectRules || projectRules.forceRefresh || forceRefresh) {
- // Check if APIserver contains 'selfsubjectrulesreviews' resource. If not switch to permissive mode.
- if (APIService.apiInfo(rulesResource)) {
- Logger.log("AuthorizationService, loading user rules for " + projectName + " project");
- var object = {kind: "SelfSubjectRulesReview",
- apiVersion: "v1"
- };
- DataService.create(rulesResource, null, object, {namespace: projectName}).then(
- function(data) {
- var normalizedData = normalizeRules(data.status.rules);
- var canUserAddToProject = canAddToProjectCheck(data.status.rules);
- cachedRulesByProject.put(projectName, {rules: normalizedData,
- canAddToProject: canUserAddToProject,
- forceRefresh: false,
- cacheTimestamp: _.now()
- });
- deferred.resolve();
- }, function() {
- permissiveMode = true;
- deferred.resolve();
- });
- } else {
- Logger.log("AuthorizationService, resource 'selfsubjectrulesreviews' is not part of APIserver. Switching into permissive mode.");
- permissiveMode = true;
- deferred.resolve();
- }
- } else {
- // Using cached data.
- Logger.log("AuthorizationService, using cached rules for " + projectName + " project");
- if ((_.now() - projectRules.cacheTimestamp) >= 600000) {
- projectRules.forceRefresh = true;
- }
- deferred.resolve();
- }
- return deferred.promise;
- };
-
- var getRulesForProject = function(projectName) {
- return _.get(cachedRulesByProject.get(projectName || currentProject), ['rules']);
- };
-
- // _canI checks whether any rule allows the specified verb (directly or via a wildcard verb) on the literal group and resource.
- var _canI = function(rules, verb, group, resource) {
- var resources = rules[group];
- if (!resources) {
- return false;
- }
- var verbs = resources[resource];
- if (!verbs) {
- return false;
- }
- return _.contains(verbs, verb) || _.contains(verbs, '*');
- };
-
- // canI checks whether any rule allows the specified verb on the specified group-resource (directly or via a wildcard rule).
- var canI = function(resource, verb, projectName) {
- if (permissiveMode) {
- return true;
- }
-
- // normalize to structured form
- var r = APIService.toResourceGroupVersion(resource);
- var rules = getRulesForProject(projectName || currentProject);
- if (!rules) {
- return false;
- }
- return _canI(rules, verb, r.group, r.resource) ||
- _canI(rules, verb, '*', '*' ) ||
- _canI(rules, verb, r.group, '*' ) ||
- _canI(rules, verb, '*', r.resource);
- };
-
- var canIAddToProject = function(projectName) {
- if (permissiveMode) {
- return true;
- } else {
- return !!_.get(cachedRulesByProject.get(projectName || currentProject), ['canAddToProject']);
- }
- };
-
- return {
- checkResource: checkResource,
- getProjectRules: getProjectRules,
- canI: canI,
- canIAddToProject: canIAddToProject,
- getRulesForProject: getRulesForProject
- };
- });
diff --git a/app/scripts/services/constants.js b/app/scripts/services/constants.js
deleted file mode 100644
index 26b10d75ad..0000000000
--- a/app/scripts/services/constants.js
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict';
-
-angular.module('openshiftConsole')
- .factory('Constants', function() {
- var constants = _.clone(window.OPENSHIFT_CONSTANTS || {});
- var version = _.clone(window.OPENSHIFT_VERSION || {});
- constants.VERSION = version;
- return constants;
- });
diff --git a/app/scripts/services/containerWebSocket.js b/app/scripts/services/containerWebSocket.js
new file mode 100644
index 0000000000..878e29b06a
--- /dev/null
+++ b/app/scripts/services/containerWebSocket.js
@@ -0,0 +1,14 @@
+'use strict';
+
+/* A WebSocket factory for kubernetesContainerTerminal */
+angular.module('openshiftConsole')
+.factory("ContainerWebSocket", function(API_CFG, $ws) {
+ return function AuthWebSocket(url, protocols) {
+ var scheme;
+ if (url.indexOf("/") === 0) {
+ scheme = window.location.protocol === "http:" ? "ws://" : "wss://";
+ url = scheme + API_CFG.openshift.hostPort + url;
+ }
+ return $ws({ url: url, method: "WATCH", protocols: protocols, auth: {} });
+ };
+});
diff --git a/app/scripts/services/data.js b/app/scripts/services/data.js
deleted file mode 100644
index 153731f30b..0000000000
--- a/app/scripts/services/data.js
+++ /dev/null
@@ -1,1311 +0,0 @@
-'use strict';
-/* jshint eqeqeq: false, unused: false, expr: true */
-
-angular.module('openshiftConsole')
-.factory('DataService', function($cacheFactory, $http, $ws, $rootScope, $q, API_CFG, APIService, Notification, Logger, $timeout, base64, base64util) {
-
- function Data(array) {
- this._data = {};
- this._objectsByAttribute(array, "metadata.name", this._data);
- }
-
- Data.prototype.by = function(attr) {
- // TODO store already generated indices
- if (attr === "metadata.name") {
- return this._data;
- }
- var map = {};
- for (var key in this._data) {
- _objectByAttribute(this._data[key], attr, map, null);
- }
- return map;
-
- };
-
- Data.prototype.update = function(object, action) {
- _objectByAttribute(object, "metadata.name", this._data, action);
- };
-
-
- // actions is whether the object was (ADDED|DELETED|MODIFIED). ADDED is assumed if actions is not
- // passed. If objects is a hash then actions must be a hash with the same keys. If objects is an array
- // then actions must be an array of the same order and length.
- Data.prototype._objectsByAttribute = function(objects, attr, map, actions) {
- angular.forEach(objects, function(obj, key) {
- _objectByAttribute(obj, attr, map, actions ? actions[key] : null);
- });
- };
-
- // Handles attr with dot notation
- // TODO write lots of tests for this helper
- // Note: this lives outside the Data prototype for now so it can be used by the helper in DataService as well
- function _objectByAttribute(obj, attr, map, action) {
- var subAttrs = attr.split(".");
- var attrValue = obj;
- for (var i = 0; i < subAttrs.length; i++) {
- attrValue = attrValue[subAttrs[i]];
- if (attrValue === undefined) {
- return;
- }
- }
-
- if ($.isArray(attrValue)) {
- // TODO implement this when we actually need it
- }
- else if ($.isPlainObject(attrValue)) {
- for (var key in attrValue) {
- var val = attrValue[key];
- if (!map[key]) {
- map[key] = {};
- }
- if (action === "DELETED") {
- delete map[key][val];
- }
- else {
- map[key][val] = obj;
- }
- }
- }
- else {
- if (action === "DELETED") {
- delete map[attrValue];
- }
- else {
- map[attrValue] = obj;
- }
- }
- }
-
- function DataService() {
- this._listDeferredMap = {};
- this._watchCallbacksMap = {};
- this._watchObjectCallbacksMap = {};
- this._watchOperationMap = {};
- this._listOperationMap = {};
- this._resourceVersionMap = {};
- this._dataCache = $cacheFactory('dataCache', {
- // 25 is a reasonable number to keep at least one or two projects worth of data in cache
- number: 25
- });
- this._immutableDataCache = $cacheFactory('immutableDataCache', {
- // 50 is a reasonable number for the immutable resources that are stored per resource instead of grouped by type
- number: 50
- });
- this._watchOptionsMap = {};
- this._watchWebsocketsMap = {};
- this._watchPollTimeoutsMap = {};
- this._websocketEventsMap = {};
-
- var self = this;
- $rootScope.$on( "$routeChangeStart", function(event, next, current) {
- self._websocketEventsMap = {};
- });
- }
-
-// resource: API resource (e.g. "pods")
-// context: API context (e.g. {project: "..."})
-// callback: (optional) function to be called with the list of the requested resource and context,
-// parameters passed to the callback:
-// Data: a Data object containing the (context-qualified) results
-// which includes a helper method for returning a map indexed
-// by attribute (e.g. data.by('metadata.name'))
-// opts: http - options to pass to the inner $http call
-//
-// returns a promise
- DataService.prototype.list = function(resource, context, callback, opts) {
- resource = APIService.toResourceGroupVersion(resource);
- var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
- var deferred = this._listDeferred(key);
- if (callback) {
- deferred.promise.then(callback);
- }
-
- if (this._isCached(key)) {
- // A watch operation is running, and we've already received the
- // initial set of data for this resource
- deferred.resolve(this._data(key));
- }
- else if (this._listInFlight(key)) {
- // no-op, our callback will get called when listOperation completes
- }
- else {
- this._startListOp(resource, context, opts);
- }
- return deferred.promise;
- };
-
-// resource: API resource (e.g. "pods")
-// name: API name, the unique name for the object
-// context: API context (e.g. {project: "..."})
-// opts:
-// http - options to pass to the inner $http call
-// gracePeriodSeconds - duration in seconds to wait before deleting the resource
-// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
- DataService.prototype.delete = function(resource, name, context, opts) {
- resource = APIService.toResourceGroupVersion(resource);
- opts = opts || {};
- var deferred = $q.defer();
- var self = this;
- var data, headers = {};
- // Differentiate between 0 and undefined
- if (_.has(opts, 'gracePeriodSeconds')) {
- data = {
- kind: "DeleteOptions",
- apiVersion: "v1",
- gracePeriodSeconds: opts.gracePeriodSeconds
- };
- headers['Content-Type'] = 'application/json';
- }
- this._getNamespace(resource, context, opts).then(function(ns){
- $http(angular.extend({
- method: 'DELETE',
- auth: {},
- data: data,
- headers: headers,
- url: self._urlForResource(resource, name, context, false, ns)
- }, opts.http || {}))
- .success(function(data, status, headerFunc, config, statusText) {
- deferred.resolve(data);
- })
- .error(function(data, status, headers, config) {
- deferred.reject({
- data: data,
- status: status,
- headers: headers,
- config: config
- });
- });
- });
- return deferred.promise;
- };
-
-
-// resource: API resource (e.g. "pods")
-// name: API name, the unique name for the object
-// object: API object data(eg. { kind: "Build", parameters: { ... } } )
-// context: API context (e.g. {project: "..."})
-// opts: http - options to pass to the inner $http call
-// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
- DataService.prototype.update = function(resource, name, object, context, opts) {
- resource = APIService.deriveTargetResource(resource, object);
- opts = opts || {};
- var deferred = $q.defer();
- var self = this;
- this._getNamespace(resource, context, opts).then(function(ns){
- $http(angular.extend({
- method: 'PUT',
- auth: {},
- data: object,
- url: self._urlForResource(resource, name, context, false, ns)
- }, opts.http || {}))
- .success(function(data, status, headerFunc, config, statusText) {
- deferred.resolve(data);
- })
- .error(function(data, status, headers, config) {
- deferred.reject({
- data: data,
- status: status,
- headers: headers,
- config: config
- });
- });
- });
- return deferred.promise;
- };
-
-// TODO: Enable PATCH when it's added to CORS Access-Control-Allow-Methods
-
-// resource: API resource group version object (e.g. { group: "", resource: "pods", version: "v1" }).
-// Must be the full resource group version because it can't be derived from the patch object.
-// name: API name, the unique name for the object
-// object: API object data(eg. { kind: "Build", parameters: { ... } } )
-// context: API context (e.g. {project: "..."})
-// opts: http - options to pass to the inner $http call
-// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
-// DataService.prototype.patch = function(resourceGroupVersion, name, object, context, opts) {
-// opts = opts || {};
-// var deferred = $q.defer();
-// var self = this;
-// this._getNamespace(resourceGroupVersion, context, opts).then(function(ns){
-// $http(angular.extend({
-// method: 'PATCH',
-// auth: {},
-// data: object,
-// url: self._urlForResource(resourceGroupVersion, name, context, false, ns)
-// }, opts.http || {}))
-// .success(function(data, status, headerFunc, config, statusText) {
-// deferred.resolve(data);
-// })
-// .error(function(data, status, headers, config) {
-// deferred.reject({
-// data: data,
-// status: status,
-// headers: headers,
-// config: config
-// });
-// });
-// });
-// return deferred.promise;
-// };
-
-// resource: API resource (e.g. "pods")
-// name: API name, the unique name for the object.
-// In case the name of the Object is provided, expected format of 'resource' parameter is 'resource/subresource', eg: 'buildconfigs/instantiate'.
-// object: API object data(eg. { kind: "Build", parameters: { ... } } )
-// context: API context (e.g. {project: "..."})
-// opts: http - options to pass to the inner $http call
-// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
- DataService.prototype.create = function(resource, name, object, context, opts) {
- resource = APIService.deriveTargetResource(resource, object);
- opts = opts || {};
- var deferred = $q.defer();
- var self = this;
- this._getNamespace(resource, context, opts).then(function(ns){
- $http(angular.extend({
- method: 'POST',
- auth: {},
- data: object,
- url: self._urlForResource(resource, name, context, false, ns)
- }, opts.http || {}))
- .success(function(data, status, headerFunc, config, statusText) {
- deferred.resolve(data);
- })
- .error(function(data, status, headers, config) {
- deferred.reject({
- data: data,
- status: status,
- headers: headers,
- config: config
- });
- });
- });
- return deferred.promise;
- };
-
- // objects: Array of API object data(eg. [{ kind: "Build", parameters: { ... } }] )
- // context: API context (e.g. {project: "..."})
- // opts: action - defines the REST action that will be called
- // - available actions: create, update
- // http - options to pass to the inner $http call
- // Returns a promise resolved with an an object like: { success: [], failure: [] }
- // where success and failure contain an array of results from the individual
- // create calls.
- DataService.prototype.batch = function(objects, context, action, opts) {
- var deferred = $q.defer();
- var successResults = [];
- var failureResults = [];
- var self = this;
- var remaining = objects.length;
- action = action || 'create';
-
- function _checkDone() {
- if (remaining === 0) {
- deferred.resolve({ success: successResults, failure: failureResults });
- }
- }
-
- _.each(objects, function(object) {
- var resource = APIService.objectToResourceGroupVersion(object);
- if (!resource) {
- // include the original object, so the error handler can display the kind/name
- failureResults.push({object: object, data: {message: APIService.invalidObjectKindOrVersion(object)}});
- remaining--;
- _checkDone();
- return;
- }
- if (!APIService.apiInfo(resource)) {
- // include the original object, so the error handler can display the kind/name
- failureResults.push({object: object, data: {message: APIService.unsupportedObjectKindOrVersion(object)}});
- remaining--;
- _checkDone();
- return;
- }
-
- var success = function(data) {
- // include the original object, so the error handler can display the kind/name
- data.object = object;
- successResults.push(data);
- remaining--;
- _checkDone();
- };
- var failure = function(data) {
- // include the original object, so the handler can display the kind/name
- data.object = object;
- failureResults.push(data);
- remaining--;
- _checkDone();
- };
-
- switch(action) {
- case "create":
- self.create(resource, null, object, context, opts).then(success, failure);
- break;
- case "update":
- self.update(resource, object.metadata.name, object, context, opts).then(success, failure);
- break;
- default:
- // default case to prevent unspecified actions and typos
- return deferred.reject({
- data: "Invalid '" + action + "' action.",
- status: 400,
- headers: function() { return null; },
- config: {},
- object: object
- });
- }
- });
- return deferred.promise;
- };
-
-// resource: API resource (e.g. "pods")
-// name: API name, the unique name for the object
-// context: API context (e.g. {project: "..."})
-// opts: force - always request (default is false)
-// http - options to pass to the inner $http call
-// errorNotification - will popup an error notification if the API request fails (default true)
- DataService.prototype.get = function(resource, name, context, opts) {
- resource = APIService.toResourceGroupVersion(resource);
- opts = opts || {};
- var key = this._uniqueKey(resource, name, context, _.get(opts, 'http.params'));
- var force = !!opts.force;
- delete opts.force;
-
- var deferred = $q.defer();
-
- var existingImmutableData = this._immutableData(key);
-
- // special case, if we have an immutable item, we can return it immediately
- if (this._hasImmutable(resource, existingImmutableData, name)) {
- $timeout(function() {
- // we can be guaranteed this wont change on us, just send what we have in existingData
- deferred.resolve(existingImmutableData.by('metadata.name')[name]);
- }, 0);
- }
- else {
- var self = this;
- this._getNamespace(resource, context, opts).then(function(ns){
- $http(angular.extend({
- method: 'GET',
- auth: {},
- url: self._urlForResource(resource, name, context, false, ns)
- }, opts.http || {}))
- .success(function(data, status, headerFunc, config, statusText) {
- if (self._isImmutable(resource)) {
- if (!existingImmutableData) {
- self._immutableData(key, [data]);
- }
- else {
- existingImmutableData.update(data, "ADDED");
- }
- }
- deferred.resolve(data);
- })
- .error(function(data, status, headers, config) {
- if (opts.errorNotification !== false) {
- var msg = "Failed to get " + resource + "/" + name;
- if (status !== 0) {
- msg += " (" + status + ")";
- }
- Notification.error(msg);
- }
- deferred.reject({
- data: data,
- status: status,
- headers: headers,
- config: config
- });
- });
- });
- }
- return deferred.promise;
- };
-
-// TODO (bpeterse): Create a new Streamer service & get this out of DataService.
-DataService.prototype.createStream = function(resource, name, context, opts, isRaw) {
- var self = this;
- resource = APIService.toResourceGroupVersion(resource);
-
- var protocols = isRaw ? 'binary.k8s.io' : 'base64.binary.k8s.io';
- var identifier = 'stream_';
- var openQueue = {};
- var messageQueue = {};
- var closeQueue = {};
- var errorQueue = {};
-
- var stream;
- var makeStream = function() {
- return self._getNamespace(resource, context, {})
- .then(function(params) {
- var cumulativeBytes = 0;
- return $ws({
- url: self._urlForResource(resource, name, context, true, _.extend(params, opts)),
- auth: {},
- onopen: function(evt) {
- _.each(openQueue, function(fn) {
- fn(evt);
- });
- },
- onmessage: function(evt) {
- if(!_.isString(evt.data)) {
- Logger.log('log stream response is not a string', evt.data);
- return;
- }
-
- var message;
- if(!isRaw) {
- message = base64.decode(base64util.pad(evt.data));
- // Count bytes for log streams, which will stop when limitBytes is reached.
- // There's no other way to detect we've reach the limit currently.
- cumulativeBytes += message.length;
- }
-
- _.each(messageQueue, function(fn) {
- if(isRaw) {
- fn(evt.data);
- } else {
- fn(message, evt.data, cumulativeBytes);
- }
- });
- },
- onclose: function(evt) {
- _.each(closeQueue, function(fn) {
- fn(evt);
- });
- },
- onerror: function(evt) {
- _.each(errorQueue, function(fn) {
- fn(evt);
- });
- },
- protocols: protocols
- }).then(function(ws) {
- Logger.log("Streaming pod log", ws);
- return ws;
- });
- });
- };
- return {
- onOpen: function(fn) {
- if(!_.isFunction(fn)) {
- return;
- }
- var id = _.uniqueId(identifier);
- openQueue[id] = fn;
- return id;
- },
- onMessage: function(fn) {
- if(!_.isFunction(fn)) {
- return;
- }
- var id = _.uniqueId(identifier);
- messageQueue[id] = fn;
- return id;
- },
- onClose: function(fn) {
- if(!_.isFunction(fn)) {
- return;
- }
- var id = _.uniqueId(identifier);
- closeQueue[id] = fn;
- return id;
- },
- onError: function(fn) {
- if(!_.isFunction(fn)) {
- return;
- }
- var id = _.uniqueId(identifier);
- errorQueue[id] = fn;
- return id;
- },
- // can remove any callback from open, message, close or error
- remove: function(id) {
- if (openQueue[id]) { delete openQueue[id]; }
- if (messageQueue[id]) { delete messageQueue[id]; }
- if (closeQueue[id]) { delete closeQueue[id]; }
- if (errorQueue[id]) { delete errorQueue[id]; }
- },
- start: function() {
- stream = makeStream();
- return stream;
- },
- stop: function() {
- stream.then(function(ws) {
- ws.close();
- });
- }
- };
-};
-
-
-// resource: API resource (e.g. "pods")
-// context: API context (e.g. {project: "..."})
-// callback: optional function to be called with the initial list of the requested resource,
-// and when updates are received, parameters passed to the callback:
-// Data: a Data object containing the (context-qualified) results
-// which includes a helper method for returning a map indexed
-// by attribute (e.g. data.by('metadata.name'))
-// event: specific event that caused this call ("ADDED", "MODIFIED",
-// "DELETED", or null) callbacks can optionally use this to
-// more efficiently process updates
-// obj: specific object that caused this call (may be null if the
-// entire list was updated) callbacks can optionally use this
-// to more efficiently process updates
-// opts: options
-// poll: true | false - whether to poll the server instead of opening
-// a websocket. Default is false.
-// pollInterval: in milliseconds, how long to wait between polling the server
-// only applies if poll=true. Default is 5000.
-// http: similar to .get, etc. at this point, only used to pass http.params for filtering
-// errorNotification: will popup an error notification if the API request fails (default true)
-// returns handle to the watch, needed to unwatch e.g.
-// var handle = DataService.watch(resource,context,callback[,opts])
-// DataService.unwatch(handle)
- DataService.prototype.watch = function(resource, context, callback, opts) {
- resource = APIService.toResourceGroupVersion(resource);
- opts = opts || {};
- var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
- if (callback) {
- // If we were given a callback, add it
- this._watchCallbacks(key).add(callback);
- }
- else if (!this._watchCallbacks(key).has()) {
- // We can be called with no callback in order to re-run a list/watch sequence for existing callbacks
- // If there are no existing callbacks, return
- return {};
- }
-
- var existingWatchOpts = this._watchOptions(key);
- if (existingWatchOpts) {
- // Check any options for compatibility with existing watch
- if (!!existingWatchOpts.poll !== !!opts.poll) { // jshint ignore:line
- throw "A watch already exists for " + resource + " with a different polling option.";
- }
- }
- else {
- this._watchOptions(key, opts);
- }
-
- var self = this;
- if (this._isCached(key)) {
- if (callback) {
- $timeout(function() {
- callback(self._data(key));
- }, 0);
- }
- }
- else {
- if (callback) {
- var resourceVersion = this._resourceVersion(key);
- if (this._data(key)) {
- $timeout(function() {
- // If the cached data is still the latest that we have, send it to the callback
- if (resourceVersion === self._resourceVersion(key)) {
- callback(self._data(key)); // but just in case, still pull from the current data map
- }
- }, 0);
- }
- }
- if (!this._listInFlight(key)) {
- this._startListOp(resource, context, opts);
- }
- }
-
- // returned handle needs resource, context, and callback in order to unwatch
- return {
- resource: resource,
- context: context,
- callback: callback,
- opts: opts
- };
- };
-
-
-
-// resource: API resource (e.g. "pods")
-// name: API name, the unique name for the object
-// context: API context (e.g. {project: "..."})
-// callback: optional function to be called with the initial list of the requested resource,
-// and when updates are received, parameters passed to the callback:
-// obj: the requested object
-// event: specific event that caused this call ("ADDED", "MODIFIED",
-// "DELETED", or null) callbacks can optionally use this to
-// more efficiently process updates
-// opts: options
-// poll: true | false - whether to poll the server instead of opening
-// a websocket. Default is false.
-// pollInterval: in milliseconds, how long to wait between polling the server
-// only applies if poll=true. Default is 5000.
-//
-// returns handle to the watch, needed to unwatch e.g.
-// var handle = DataService.watch(resource,context,callback[,opts])
-// DataService.unwatch(handle)
- DataService.prototype.watchObject = function(resource, name, context, callback, opts) {
- resource = APIService.toResourceGroupVersion(resource);
- opts = opts || {};
- var key = this._uniqueKey(resource, name, context, _.get(opts, 'http.params'));
- var wrapperCallback;
- if (callback) {
- // If we were given a callback, add it
- this._watchObjectCallbacks(key).add(callback);
- var self = this;
- wrapperCallback = function(items, event, item) {
- // If we got an event for a single item, only fire the callback if its the item we care about
- if (item && item.metadata.name === name) {
- self._watchObjectCallbacks(key).fire(item, event);
- }
- else if (!item) {
- // Otherwise its an initial listing, see if we can find the item we care about in the list
- var itemsByName = items.by("metadata.name");
- if (itemsByName[name]) {
- self._watchObjectCallbacks(key).fire(itemsByName[name]);
- }
- }
- };
- }
- else if (!this._watchObjectCallbacks(key).has()) {
- // This block may not be needed yet, don't expect this would get called without a callback currently...
- return {};
- }
-
- // For now just watch the type, eventually we may want to do something more complicated
- // and watch just the object if the type is not already being watched
- var handle = this.watch(resource, context, wrapperCallback, opts);
- handle.objectCallback = callback;
- handle.objectName = name;
-
- return handle;
- };
-
- DataService.prototype.unwatch = function(handle) {
- var resource = handle.resource;
- var objectName = handle.objectName;
- var context = handle.context;
- var callback = handle.callback;
- var objectCallback = handle.objectCallback;
- var opts = handle.opts;
- var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
-
- if (objectCallback && objectName) {
- var objectKey = this._uniqueKey(resource, objectName, context, _.get(opts, 'http.params'));
- var objCallbacks = this._watchObjectCallbacks(objectKey);
- objCallbacks.remove(objectCallback);
- }
-
- var callbacks = this._watchCallbacks(key);
- if (callback) {
- callbacks.remove(callback);
- }
- if (!callbacks.has()) {
- if (opts && opts.poll) {
- clearTimeout(this._watchPollTimeouts(key));
- this._watchPollTimeouts(key, null);
- }
- else if (this._watchWebsockets(key)){
- // watchWebsockets may not have been set up yet if the projectPromise never resolves
- var ws = this._watchWebsockets(key);
- // Make sure the onclose listener doesn't reopen this websocket.
- ws.shouldClose = true;
- ws.close();
- this._watchWebsockets(key, null);
- }
-
- this._watchInFlight(key, false);
- this._watchOptions(key, null);
- }
- };
-
- // Takes an array of watch handles and unwatches them
- DataService.prototype.unwatchAll = function(handles) {
- for (var i = 0; i < handles.length; i++) {
- this.unwatch(handles[i]);
- }
- };
-
- DataService.prototype._watchCallbacks = function(key) {
- if (!this._watchCallbacksMap[key]) {
- this._watchCallbacksMap[key] = $.Callbacks();
- }
- return this._watchCallbacksMap[key];
- };
-
- DataService.prototype._watchObjectCallbacks = function(key) {
- if (!this._watchObjectCallbacksMap[key]) {
- this._watchObjectCallbacksMap[key] = $.Callbacks();
- }
- return this._watchObjectCallbacksMap[key];
- };
-
- DataService.prototype._listDeferred = function(key) {
- if (!this._listDeferredMap[key]) {
- this._listDeferredMap[key] = $q.defer();
- }
- return this._listDeferredMap[key];
- };
-
- DataService.prototype._watchInFlight = function(key, op) {
- if (!op && op !== false) {
- return this._watchOperationMap[key];
- }
- else {
- this._watchOperationMap[key] = op;
- }
- };
-
- DataService.prototype._listInFlight = function(key, op) {
- if (!op && op !== false) {
- return this._listOperationMap[key];
- }
- else {
- this._listOperationMap[key] = op;
- }
- };
-
- DataService.prototype._resourceVersion = function(key, rv) {
- if (!rv) {
- return this._resourceVersionMap[key];
- }
- else {
- this._resourceVersionMap[key] = rv;
- }
- };
-
- // uses $cacheFactory to impl LRU cache
- DataService.prototype._data = function(key, data) {
- return data ?
- this._dataCache.put(key, new Data(data)) :
- this._dataCache.get(key);
- };
-
- // uses $cacheFactory to impl LRU cache
- DataService.prototype._immutableData = function(key, data) {
- return data ?
- this._immutableDataCache.put(key, new Data(data)) :
- this._immutableDataCache.get(key);
- };
-
- DataService.prototype._isCached = function(key) {
- return this._watchInFlight(key) &&
- this._resourceVersion(key) &&
- (!!this._data(key));
- };
-
- DataService.prototype._watchOptions = function(key, opts) {
- if (opts === undefined) {
- return this._watchOptionsMap[key];
- }
- else {
- this._watchOptionsMap[key] = opts;
- }
- };
-
- DataService.prototype._watchPollTimeouts = function(key, timeout) {
- if (!timeout) {
- return this._watchPollTimeoutsMap[key];
- }
- else {
- this._watchPollTimeoutsMap[key] = timeout;
- }
- };
-
- DataService.prototype._watchWebsockets = function(key, timeout) {
- if (!timeout) {
- return this._watchWebsocketsMap[key];
- }
- else {
- this._watchWebsocketsMap[key] = timeout;
- }
- };
-
- // Maximum number of websocket events to track per resource/context in _websocketEventsMap.
- var maxWebsocketEvents = 10;
-
- DataService.prototype._addWebsocketEvent = function(key, eventType) {
- var events = this._websocketEventsMap[key];
- if (!events) {
- events = this._websocketEventsMap[key] = [];
- }
-
- // Add the event to the end of the array with the time in millis.
- events.push({
- type: eventType,
- time: Date.now()
- });
-
- // Only keep 10 events. Shift the array to make room for the new event.
- while (events.length > maxWebsocketEvents) { events.shift(); }
- };
-
- function isTooManyRecentEvents(events) {
- // If we've had more than 10 events in 30 seconds, stop.
- // The oldest event is at index 0.
- var recentDuration = 1000 * 30;
- return events.length >= maxWebsocketEvents && (Date.now() - events[0].time) < recentDuration;
- }
-
- function isTooManyConsecutiveCloses(events) {
- var maxConsecutiveCloseEvents = 5;
- if (events.length < maxConsecutiveCloseEvents) {
- return false;
- }
-
- // Make sure the last 5 events were not close events, which means the
- // connection is not succeeding. This check is necessary if connection
- // timeouts take longer than 6 seconds.
- for (var i = events.length - maxConsecutiveCloseEvents; i < events.length; i++) {
- if (events[i].type !== 'close') {
- return false;
- }
- }
-
- return true;
- }
-
- DataService.prototype._isTooManyWebsocketRetries = function(key) {
- var events = this._websocketEventsMap[key];
- if (!events) {
- return false;
- }
-
- if (isTooManyRecentEvents(events)) {
- Logger.log("Too many websocket open or close events for resource/context in a short period", key, events);
- return true;
- }
-
- if (isTooManyConsecutiveCloses(events)) {
- Logger.log("Too many consecutive websocket close events for resource/context", key, events);
- return true;
- }
-
- return false;
- };
-
-
- // will take an object, filter & sort it for consistent unique key generation
- // uses encodeURIComponent internally because keys can have special characters, such as '='
- var paramsForKey = function(params) {
- var keys = _.keysIn(
- _.pick(
- params,
- ['fieldSelector', 'labelSelector'])
- ).sort();
- return _.reduce(
- keys,
- function(result, key, i) {
- return result + key + '=' + encodeURIComponent(params[key]) +
- ((i < (keys.length-1)) ? '&' : '');
- }, '?');
-
- };
-
-
- // - creates a unique key representing a resource in its context (project)
- // - primary use case for caching
- // - proxies to _urlForResource to generate unique keys
- // - ensure namespace if available
- // - ensure only witelisted url params used for keys (fieldSelector, labelSelector) via paramsForKey
- // and that these are consistently ordered
- // - NOTE: Do not use the key as your url for API requests. This function does not use the 'isWebsocket'
- // bool. Both websocket & http operations should respond with the same data from cache if key matches
- // so the unique key will always include http://
- DataService.prototype._uniqueKey = function(resource, name, context, params) {
- var ns = context && context.namespace ||
- _.get(context, 'project.metadata.name') ||
- context.projectName;
- return this._urlForResource(resource, name, context, null, angular.extend({}, {}, {namespace: ns})).toString() + paramsForKey(params || {});
- };
-
-
- DataService.prototype._startListOp = function(resource, context, opts) {
- opts = opts || {};
- var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
- // mark the operation as in progress
- this._listInFlight(key, true);
-
- var self = this;
- if (context.projectPromise && !resource.equals("projects")) {
- context.projectPromise.done(function(project) {
- $http(angular.extend({
- method: 'GET',
- auth: {},
- url: self._urlForResource(resource, null, context, false, {namespace: project.metadata.name})
- }, opts.http || {}))
- .success(function(data, status, headerFunc, config, statusText) {
- self._listOpComplete(key, resource, context, opts, data);
- }).error(function(data, status, headers, config) {
- // mark list op as complete
- self._listInFlight(key, false);
- var deferred = self._listDeferred(key);
- delete self._listDeferredMap[key];
- deferred.reject(data, status, headers, config);
-
- if (!_.get(opts, 'errorNotification', true)) {
- return;
- }
-
- var msg = "Failed to list " + resource;
- if (status !== 0) {
- msg += " (" + status + ")";
- }
- Notification.error(msg);
- });
- });
- }
- else {
- $http({
- method: 'GET',
- auth: {},
- url: this._urlForResource(resource, null, context),
- }).success(function(data, status, headerFunc, config, statusText) {
- self._listOpComplete(key, resource, context, opts, data);
- }).error(function(data, status, headers, config) {
- // mark list op as complete
- self._listInFlight(key, false);
- var deferred = self._listDeferred(key);
- delete self._listDeferredMap[key];
- deferred.reject(data, status, headers, config);
-
- if (!_.get(opts, 'errorNotification', true)) {
- return;
- }
-
- var msg = "Failed to list " + resource;
- if (status !== 0) {
- msg += " (" + status + ")";
- }
- Notification.error(msg);
- });
- }
- };
-
- DataService.prototype._listOpComplete = function(key, resource, context, opts, data) {
- if (!data.items) {
- console.warn("List request for " + resource + " returned a null items array. This is an invalid API response.");
- }
- var items = data.items || [];
- // Here we normalize all items to have a kind property.
- // One of the warts in the kubernetes REST API is that items retrieved
- // via GET on a list resource won't have a kind property set.
- // See: https://github.com/kubernetes/kubernetes/issues/3030
- if (data.kind && data.kind.indexOf("List") === data.kind.length - 4) {
- angular.forEach(items, function(item) {
- if (!item.kind) {
- item.kind = data.kind.slice(0, -4);
- }
- if (!item.apiVersion) {
- item.apiVersion = data.apiVersion;
- }
- });
- }
-
- // mark list op as complete
- this._listInFlight(key, false);
- var deferred = this._listDeferred(key);
- delete this._listDeferredMap[key];
-
- this._resourceVersion(key, data.resourceVersion || data.metadata.resourceVersion);
- this._data(key, items);
- deferred.resolve(this._data(key));
- this._watchCallbacks(key).fire(this._data(key));
-
- if (this._watchCallbacks(key).has()) {
- var watchOpts = this._watchOptions(key) || {};
- if (watchOpts.poll) {
- this._watchInFlight(key, true);
- this._watchPollTimeouts(key, setTimeout($.proxy(this, "_startListOp", resource, context), watchOpts.pollInterval || 5000));
- }
- else if (!this._watchInFlight(key)) {
- this._startWatchOp(key, resource, context, opts, this._resourceVersion(key));
- }
- }
- };
-
- DataService.prototype._startWatchOp = function(key, resource, context, opts, resourceVersion) {
- this._watchInFlight(key, true);
- // Note: current impl uses one websocket per resource
- // eventually want a single websocket connection that we
- // send a subscription request to for each resource
-
- // Only listen for updates if websockets are available
- if ($ws.available()) {
- var self = this;
- var params = _.get(opts, 'http.params') || {};
- params.watch = true;
- if (resourceVersion) {
- params.resourceVersion = resourceVersion;
- }
- if (context.projectPromise && !resource.equals("projects")) {
- context.projectPromise.done(function(project) {
- params.namespace = project.metadata.name;
- $ws({
- method: "WATCH",
- url: self._urlForResource(resource, null, context, true, params),
- auth: {},
- onclose: $.proxy(self, "_watchOpOnClose", resource, context, opts),
- onmessage: $.proxy(self, "_watchOpOnMessage", resource, context, opts),
- onopen: $.proxy(self, "_watchOpOnOpen", resource, context, opts)
- }).then(function(ws) {
- Logger.log("Watching", ws);
- self._watchWebsockets(key, ws);
- });
- });
- }
- else {
- $ws({
- method: "WATCH",
- url: self._urlForResource(resource, null, context, true, params),
- auth: {},
- onclose: $.proxy(self, "_watchOpOnClose", resource, context, opts),
- onmessage: $.proxy(self, "_watchOpOnMessage", resource, context, opts),
- onopen: $.proxy(self, "_watchOpOnOpen", resource, context, opts)
- }).then(function(ws){
- Logger.log("Watching", ws);
- self._watchWebsockets(key, ws);
- });
- }
- }
- };
-
- DataService.prototype._watchOpOnOpen = function(resource, context, opts, event) {
- Logger.log('Websocket opened for resource/context', resource, context);
- var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
- this._addWebsocketEvent(key, 'open');
- };
-
- DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) {
- var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
- try {
- var eventData = $.parseJSON(event.data);
-
- if (eventData.type == "ERROR") {
- Logger.log("Watch window expired for resource/context", resource, context);
- if (event.target) {
- event.target.shouldRelist = true;
- }
- return;
- }
- else if (eventData.type === "DELETED") {
- // Add this ourselves since the API doesn't add anything
- // this way the views can use it to trigger special behaviors
- if (eventData.object && eventData.object.metadata && !eventData.object.metadata.deletionTimestamp) {
- eventData.object.metadata.deletionTimestamp = (new Date()).toISOString();
- }
- }
-
- if (eventData.object) {
- this._resourceVersion(key, eventData.object.resourceVersion || eventData.object.metadata.resourceVersion);
- }
- // TODO do we reset all the by() indices, or simply update them, since we should know what keys are there?
- // TODO let the data object handle its own update
- this._data(key).update(eventData.object, eventData.type);
- var self = this;
- // Wrap in a $timeout which will trigger an $apply to mirror $http callback behavior
- // without timeout this is triggering a repeated digest loop
- $timeout(function() {
- self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object);
- }, 0);
- }
- catch (e) {
- // TODO: surface in the UI?
- Logger.error("Error processing message", resource, event.data);
- }
- };
-
- DataService.prototype._watchOpOnClose = function(resource, context, opts, event) {
- var eventWS = event.target;
- var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
-
- if (!eventWS) {
- Logger.log("Skipping reopen, no eventWS in event", event);
- return;
- }
-
- var registeredWS = this._watchWebsockets(key);
- if (!registeredWS) {
- Logger.log("Skipping reopen, no registeredWS for resource/context", resource, context);
- return;
- }
-
- // Don't reopen a web socket that is no longer registered for this resource/context
- if (eventWS !== registeredWS) {
- Logger.log("Skipping reopen, eventWS does not match registeredWS", eventWS, registeredWS);
- return;
- }
-
- // We are the registered web socket for this resource/context, and we are no longer in flight
- // Unlock this resource/context in case we decide not to reopen
- this._watchInFlight(key, false);
-
- // Don't reopen web sockets we closed ourselves
- if (eventWS.shouldClose) {
- Logger.log("Skipping reopen, eventWS was explicitly closed", eventWS);
- return;
- }
-
- // Don't reopen clean closes (for example, navigating away from the page to example.com)
- if (event.wasClean) {
- Logger.log("Skipping reopen, clean close", event);
- return;
- }
-
- // Don't reopen if no one is listening for this data any more
- if (!this._watchCallbacks(key).has()) {
- Logger.log("Skipping reopen, no listeners registered for resource/context", resource, context);
- return;
- }
-
- // Don't reopen if we've failed this resource/context too many times
- if (this._isTooManyWebsocketRetries(key)) {
- // Show an error notication unless disabled in opts.
- if (_.get(opts, 'errorNotification', true)) {
- Notification.error("Server connection interrupted.", {
- id: "websocket_retry_halted",
- mustDismiss: true,
- actions: {
- refresh: {label: "Refresh", action: function() { window.location.reload(); }}
- }
- });
- }
- return;
- }
-
- // Keep track of this event.
- this._addWebsocketEvent(key, 'close');
-
- // If our watch window expired, we have to relist to get a new resource version to watch from
- if (eventWS.shouldRelist) {
- Logger.log("Relisting for resource/context", resource, context);
- // Restart a watch() from the beginning, which triggers a list/watch sequence
- // The watch() call is responsible for setting _watchInFlight back to true
- // Add a short delay to avoid a scenario where we make non-stop requests
- // When the timeout fires, if no callbacks are registered for this
- // resource/context, or if a watch is already in flight, `watch()` is a no-op
- var self = this;
- setTimeout(function() {
- self.watch(resource, context);
- }, 2000);
- return;
- }
-
- // Attempt to re-establish the connection after a two-second back-off
- // Re-mark ourselves as in-flight to prevent other callers from jumping in in the meantime
- Logger.log("Rewatching for resource/context", resource, context);
- this._watchInFlight(key, true);
- setTimeout(
- $.proxy(this, "_startWatchOp", key, resource, context, opts, this._resourceVersion(key)),
- 2000
- );
- };
-
- var URL_ROOT_TEMPLATE = "{protocol}://{+hostPort}{+prefix}{/group}/{version}/";
- var URL_GET_LIST = URL_ROOT_TEMPLATE + "{resource}{?q*}";
- var URL_OBJECT = URL_ROOT_TEMPLATE + "{resource}/{name}{/subresource*}{?q*}";
- var URL_NAMESPACED_GET_LIST = URL_ROOT_TEMPLATE + "namespaces/{namespace}/{resource}{?q*}";
- var URL_NAMESPACED_OBJECT = URL_ROOT_TEMPLATE + "namespaces/{namespace}/{resource}/{name}{/subresource*}{?q*}";
-
-
- DataService.prototype._urlForResource = function(resource, name, context, isWebsocket, params) {
- var apiInfo = APIService.apiInfo(resource);
- if (!apiInfo) {
- Logger.error("_urlForResource called with unknown resource", resource, arguments);
- return null;
- }
-
- var protocol;
- params = params || {};
- if (isWebsocket) {
- protocol = window.location.protocol === "http:" ? "ws" : "wss";
- }
- else {
- protocol = window.location.protocol === "http:" ? "http" : "https";
- }
-
- if (context && context.namespace && !params.namespace) {
- params.namespace = context.namespace;
- }
-
- var namespaceInPath = params.namespace;
- var namespace = null;
- if (namespaceInPath) {
- namespace = params.namespace;
- params = angular.copy(params);
- delete params.namespace;
- }
- var template;
- var templateOptions = {
- protocol: protocol,
- hostPort: apiInfo.hostPort,
- prefix: apiInfo.prefix,
- group: apiInfo.group,
- version: apiInfo.version,
- resource: resource.primaryResource(),
- subresource: resource.subresources(),
- name: name,
- namespace: namespace,
- q: params
- };
- if (name) {
- template = namespaceInPath ? URL_NAMESPACED_OBJECT : URL_OBJECT;
- }
- else {
- template = namespaceInPath ? URL_NAMESPACED_GET_LIST : URL_GET_LIST;
- }
- return URI.expand(template, templateOptions).toString();
- };
-
- DataService.prototype.url = function(options) {
- if (options && options.resource) {
- var opts = angular.copy(options);
- delete opts.resource;
- delete opts.group;
- delete opts.version;
- delete opts.name;
- delete opts.isWebsocket;
- var resource = APIService.toResourceGroupVersion({
- resource: options.resource,
- group: options.group,
- version: options.version
- });
- return this._urlForResource(resource, options.name, null, !!options.isWebsocket, opts);
- }
- return null;
- };
-
- DataService.prototype.openshiftAPIBaseUrl = function() {
- var protocol = window.location.protocol === "http:" ? "http" : "https";
- var hostPort = API_CFG.openshift.hostPort;
- return new URI({protocol: protocol, hostname: hostPort}).toString();
- };
-
- // Immutables are flagged here as we should not need to fetch them more than once.
- var IMMUTABLE_RESOURCE = {
- imagestreamimages: true
- };
-
- // - request once and never need to request again, these do not change!
- DataService.prototype._isImmutable = function(resource) {
- return !!IMMUTABLE_RESOURCE[resource.resource];
- };
-
- // do we already have the data for this?
- DataService.prototype._hasImmutable = function(resource, existingData, name) {
- return this._isImmutable(resource) && existingData && existingData.by('metadata.name')[name];
- };
-
- DataService.prototype._getNamespace = function(resource, context, opts) {
- var deferred = $q.defer();
- if (opts.namespace) {
- deferred.resolve({namespace: opts.namespace});
- }
- else if (context.projectPromise && !resource.equals("projects")) {
- context.projectPromise.done(function(project) {
- deferred.resolve({namespace: project.metadata.name});
- });
- }
- else {
- deferred.resolve(null);
- }
- return deferred.promise;
- };
-
- return new DataService();
-});
diff --git a/app/scripts/services/logger.js b/app/scripts/services/logger.js
deleted file mode 100644
index 74335fdd7c..0000000000
--- a/app/scripts/services/logger.js
+++ /dev/null
@@ -1,43 +0,0 @@
-'use strict';
-
-angular.module('openshiftConsole')
-.provider('Logger', function() {
- this.$get = function() {
- // Wraps the global Logger from https://github.com/jonnyreeves/js-logger
- var OSLogger = Logger.get("OpenShift");
- var logger = {
- get: function(name) {
- var logger = Logger.get("OpenShift/" + name);
- var logLevel = "OFF";
- if (localStorage) {
- logLevel = localStorage['OpenShiftLogLevel.' + name] || logLevel;
- }
- logger.setLevel(Logger[logLevel]);
- return logger;
- },
- log: function() {
- OSLogger.log.apply(OSLogger, arguments);
- },
- info: function() {
- OSLogger.info.apply(OSLogger, arguments);
- },
- debug: function() {
- OSLogger.debug.apply(OSLogger, arguments);
- },
- warn: function() {
- OSLogger.warn.apply(OSLogger, arguments);
- },
- error: function() {
- OSLogger.error.apply(OSLogger, arguments);
- }
- };
-
- // Set default log level
- var logLevel = "ERROR";
- if (localStorage) {
- logLevel = localStorage['OpenShiftLogLevel.main'] || logLevel;
- }
- OSLogger.setLevel(Logger[logLevel]);
- return logger;
- };
-});
\ No newline at end of file
diff --git a/app/scripts/services/login.js b/app/scripts/services/login.js
deleted file mode 100644
index 9510f68cc3..0000000000
--- a/app/scripts/services/login.js
+++ /dev/null
@@ -1,174 +0,0 @@
-'use strict';
-
-// Login strategies
-angular.module('openshiftConsole')
-.provider('RedirectLoginService', function() {
- var _oauth_client_id = "";
- var _oauth_authorize_uri = "";
- var _oauth_redirect_uri = "";
-
- this.OAuthClientID = function(id) {
- if (id) {
- _oauth_client_id = id;
- }
- return _oauth_client_id;
- };
- this.OAuthAuthorizeURI = function(uri) {
- if (uri) {
- _oauth_authorize_uri = uri;
- }
- return _oauth_authorize_uri;
- };
- this.OAuthRedirectURI = function(uri) {
- if (uri) {
- _oauth_redirect_uri = uri;
- }
- return _oauth_redirect_uri;
- };
-
- this.$get = function($location, $q, Logger, base64) {
- var authLogger = Logger.get("auth");
-
- var getRandomInts = function(length) {
- var randomValues;
-
- if (window.crypto && window.Uint32Array) {
- try {
- var r = new Uint32Array(length);
- window.crypto.getRandomValues(r);
- randomValues = [];
- for (var j=0; j < length; j++) {
- randomValues.push(r[j]);
- }
- } catch(e) {
- authLogger.debug("RedirectLoginService.getRandomInts: ", e);
- randomValues = null;
- }
- }
-
- if (!randomValues) {
- randomValues = [];
- for (var i=0; i < length; i++) {
- randomValues.push(Math.floor(Math.random() * 4294967296));
- }
- }
-
- return randomValues;
- };
-
- var nonceKey = "RedirectLoginService.nonce";
- var makeState = function(then) {
- var nonce = String(new Date().getTime()) + "-" + getRandomInts(8).join("");
- try {
- window.localStorage[nonceKey] = nonce;
- } catch(e) {
- authLogger.log("RedirectLoginService.makeState, localStorage error: ", e);
- }
- return base64.urlencode(JSON.stringify({then: then, nonce:nonce}));
- };
- var parseState = function(state) {
- var retval = {
- then: null,
- verified: false
- };
-
- var nonce = "";
- try {
- nonce = window.localStorage[nonceKey];
- window.localStorage.removeItem(nonceKey);
- } catch(e) {
- authLogger.log("RedirectLoginService.parseState, localStorage error: ", e);
- }
-
- try {
- var data = state ? JSON.parse(base64.urldecode(state)) : {};
- if (data && data.nonce && nonce && data.nonce === nonce) {
- retval.verified = true;
- retval.then = data.then;
- }
- } catch(e) {
- authLogger.error("RedirectLoginService.parseState, state error: ", e);
- }
- authLogger.error("RedirectLoginService.parseState", retval);
- return retval;
- };
-
- return {
- // Returns a promise that resolves with {user:{...}, token:'...', ttl:X}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']}
- login: function() {
- if (_oauth_client_id === "") {
- return $q.reject({error:'invalid_request', error_description:'RedirectLoginServiceProvider.OAuthClientID() not set'});
- }
- if (_oauth_authorize_uri === "") {
- return $q.reject({error:'invalid_request', error_description:'RedirectLoginServiceProvider.OAuthAuthorizeURI() not set'});
- }
- if (_oauth_redirect_uri === "") {
- return $q.reject({error:'invalid_request', error_description:'RedirectLoginServiceProvider.OAuthRedirectURI not set'});
- }
-
- var deferred = $q.defer();
- var uri = new URI(_oauth_authorize_uri);
- // Never send a local fragment to remote servers
- var returnUri = new URI($location.url()).fragment("");
- uri.query({
- client_id: _oauth_client_id,
- response_type: 'token',
- state: makeState(returnUri.toString()),
- redirect_uri: _oauth_redirect_uri
- });
- authLogger.log("RedirectLoginService.login(), redirecting", uri.toString());
- window.location.href = uri.toString();
- // Return a promise we never intend to keep, because we're redirecting to another page
- return deferred.promise;
- },
-
- // Parses oauth callback parameters from window.location
- // Returns a promise that resolves with {token:'...',then:'...',verified:true|false}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']}
- // If no token and no error is present, resolves with {}
- // Example error codes: https://tools.ietf.org/html/rfc6749#section-5.2
- finish: function() {
- // Get url
- var u = new URI($location.url());
-
- // Read params
- var queryParams = u.query(true);
- var fragmentParams = new URI("?" + u.fragment()).query(true);
- authLogger.log("RedirectLoginService.finish()", queryParams, fragmentParams);
-
- // Error codes can come in query params or fragment params
- // Handle an error response from the OAuth server
- var error = queryParams.error || fragmentParams.error;
- if (error) {
- var error_description = queryParams.error_description || fragmentParams.error_description;
- var error_uri = queryParams.error_uri || fragmentParams.error_uri;
- authLogger.log("RedirectLoginService.finish(), error", error, error_description, error_uri);
- return $q.reject({
- error: error,
- error_description: error_description,
- error_uri: error_uri
- });
- }
-
- var stateData = parseState(fragmentParams.state);
-
- // Handle an access_token response
- if (fragmentParams.access_token && (fragmentParams.token_type || "").toLowerCase() === "bearer") {
- var deferred = $q.defer();
- deferred.resolve({
- token: fragmentParams.access_token,
- ttl: fragmentParams.expires_in,
- then: stateData.then,
- verified: stateData.verified
- });
- return deferred.promise;
- }
-
- // No token and no error is invalid
- return $q.reject({
- error: "invalid_request",
- error_description: "No API token returned"
- });
- }
- };
- };
-});
diff --git a/app/scripts/services/logout.js b/app/scripts/services/logout.js
deleted file mode 100644
index d6ea2048c8..0000000000
--- a/app/scripts/services/logout.js
+++ /dev/null
@@ -1,30 +0,0 @@
-'use strict';
-
-// Logout strategies
-angular.module('openshiftConsole')
-.provider('DeleteTokenLogoutService', function() {
-
- this.$get = function($q, $injector, Logger) {
- var authLogger = Logger.get("auth");
-
- return {
- logout: function(user, token) {
- authLogger.log("DeleteTokenLogoutService.logout()", user, token);
-
- // If we don't have a token, we're done
- if (!token) {
- authLogger.log("DeleteTokenLogoutService, no token, returning immediately");
- return $q.when({});
- }
-
- // Lazily get the data service. Can't explicitly depend on it or we get circular dependencies.
- var DataService = $injector.get('DataService');
- // Use the token to delete the token
- // Never trigger a login when deleting our token
- var opts = {http: {auth: {token: token, triggerLogin: false}}};
- // TODO: Change this to return a promise that "succeeds" even if the token delete fails?
- return DataService.delete("oauthaccesstokens", token, {}, opts);
- },
- };
- };
-});
diff --git a/app/scripts/services/notification.js b/app/scripts/services/notification.js
deleted file mode 100644
index 8cd1a1b4ac..0000000000
--- a/app/scripts/services/notification.js
+++ /dev/null
@@ -1,62 +0,0 @@
-'use strict';
-/* jshint unused: false */
-
-angular.module('openshiftConsole')
-.factory('Notification', function($rootScope) {
- function Notification() {
- this.messenger = Messenger({
- extraClasses: 'messenger-fixed messenger-on-bottom messenger-on-right',
- theme: 'flat',
- messageDefaults: {
- showCloseButton: true,
- hideAfter: 10
- }
- });
-
- var self = this;
- $rootScope.$on( "$routeChangeStart", function(event, next, current) {
- self.clear();
- });
- }
-
- // Opts:
- // id - if an id is passed only one message with this id will ever be shown
- // mustDismiss - the user must explicitly dismiss the message, it will not auto-hide
- Notification.prototype.notify = function(type, message, opts) {
- opts = opts || {};
- var notifyOpts = {
- type: type,
- // TODO report this issue upstream to messenger, they don't handle messages with invalid html
- // they should be escaping it
- message: $('').text(message).html(),
- id: opts.id,
- actions: opts.actions
- };
- if (opts.mustDismiss) {
- notifyOpts.hideAfter = false;
- }
- this.messenger.post(notifyOpts);
- };
-
- Notification.prototype.success = function(message, opts) {
- this.notify("success", message, opts);
- };
-
- Notification.prototype.info = function(message, opts) {
- this.notify("info", message, opts);
- };
-
- Notification.prototype.error = function(message, opts) {
- this.notify("error", message, opts);
- };
-
- Notification.prototype.warning = function(message, opts) {
- this.notify("warning", message, opts);
- };
-
- Notification.prototype.clear = function() {
- this.messenger.hideAll();
- };
-
- return new Notification();
-});
diff --git a/app/scripts/services/userstore.js b/app/scripts/services/userstore.js
deleted file mode 100644
index a8535870ae..0000000000
--- a/app/scripts/services/userstore.js
+++ /dev/null
@@ -1,193 +0,0 @@
-'use strict';
-/* jshint unused: false */
-
-// UserStore objects able to remember user and tokens for the current user
-angular.module('openshiftConsole')
-.provider('MemoryUserStore', function() {
- this.$get = function(Logger){
- var authLogger = Logger.get("auth");
- var _user = null;
- var _token = null;
- return {
- available: function() {
- return true;
- },
- getUser: function(){
- authLogger.log("MemoryUserStore.getUser", _user);
- return _user;
- },
- setUser: function(user, ttl) {
- // TODO: honor ttl
- authLogger.log("MemoryUserStore.setUser", user);
- _user = user;
- },
- getToken: function() {
- authLogger.log("MemoryUserStore.getToken", _token);
- return _token;
- },
- setToken: function(token, ttl) {
- // TODO: honor ttl
- authLogger.log("MemoryUserStore.setToken", token);
- _token = token;
- }
- };
- };
-})
-.provider('SessionStorageUserStore', function() {
- this.$get = function(Logger){
- var authLogger = Logger.get("auth");
- var userkey = "SessionStorageUserStore.user";
- var tokenkey = "SessionStorageUserStore.token";
- return {
- available: function() {
- try {
- var x = String(new Date().getTime());
- sessionStorage['SessionStorageUserStore.test'] = x;
- var y = sessionStorage['SessionStorageUserStore.test'];
- sessionStorage.removeItem('SessionStorageUserStore.test');
- return x === y;
- } catch(e) {
- return false;
- }
- },
- getUser: function(){
- try {
- var user = JSON.parse(sessionStorage[userkey]);
- authLogger.log("SessionStorageUserStore.getUser", user);
- return user;
- } catch(e) {
- authLogger.error("SessionStorageUserStore.getUser", e);
- return null;
- }
- },
- setUser: function(user, ttl) {
- // TODO: honor ttl
- if (user) {
- authLogger.log("SessionStorageUserStore.setUser", user);
- sessionStorage[userkey] = JSON.stringify(user);
- } else {
- authLogger.log("SessionStorageUserStore.setUser", user, "deleting");
- sessionStorage.removeItem(userkey);
- }
- },
- getToken: function() {
- try {
- var token = sessionStorage[tokenkey];
- authLogger.log("SessionStorageUserStore.getToken", token);
- return token;
- } catch(e) {
- authLogger.error("SessionStorageUserStore.getToken", e);
- return null;
- }
- },
- setToken: function(token, ttl) {
- // TODO: honor ttl
- if (token) {
- authLogger.log("SessionStorageUserStore.setToken", token);
- sessionStorage[tokenkey] = token;
- } else {
- authLogger.log("SessionStorageUserStore.setToken", token, "deleting");
- sessionStorage.removeItem(tokenkey);
- }
- }
- };
- };
-})
-.provider('LocalStorageUserStore', function() {
- this.$get = function(Logger){
- var authLogger = Logger.get("auth");
- var userkey = "LocalStorageUserStore.user";
- var tokenkey = "LocalStorageUserStore.token";
-
- var ttlKey = function(key) {
- return key + ".ttl";
- };
- var setTTL = function(key, ttl) {
- if (ttl) {
- var expires = new Date().getTime() + ttl*1000;
- localStorage[ttlKey(key)] = expires;
- authLogger.log("LocalStorageUserStore.setTTL", key, ttl, new Date(expires).toString());
- } else {
- localStorage.removeItem(ttlKey(key));
- authLogger.log("LocalStorageUserStore.setTTL deleting", key);
- }
- };
- var isTTLExpired = function(key) {
- var ttl = localStorage[ttlKey(key)];
- if (!ttl) {
- return false;
- }
- var expired = parseInt(ttl) < new Date().getTime();
- authLogger.log("LocalStorageUserStore.isTTLExpired", key, expired);
- return expired;
- };
-
- return {
- available: function() {
- try {
- var x = String(new Date().getTime());
- localStorage['LocalStorageUserStore.test'] = x;
- var y = localStorage['LocalStorageUserStore.test'];
- localStorage.removeItem('LocalStorageUserStore.test');
- return x === y;
- } catch(e) {
- return false;
- }
- },
- getUser: function(){
- try {
- if (isTTLExpired(userkey)) {
- authLogger.log("LocalStorageUserStore.getUser expired");
- localStorage.removeItem(userkey);
- setTTL(userkey, null);
- return null;
- }
- var user = JSON.parse(localStorage[userkey]);
- authLogger.log("LocalStorageUserStore.getUser", user);
- return user;
- } catch(e) {
- authLogger.error("LocalStorageUserStore.getUser", e);
- return null;
- }
- },
- setUser: function(user, ttl) {
- if (user) {
- authLogger.log("LocalStorageUserStore.setUser", user, ttl);
- localStorage[userkey] = JSON.stringify(user);
- setTTL(userkey, ttl);
- } else {
- authLogger.log("LocalStorageUserStore.setUser", user, "deleting");
- localStorage.removeItem(userkey);
- setTTL(userkey, null);
- }
- },
- getToken: function() {
- try {
- if (isTTLExpired(tokenkey)) {
- authLogger.log("LocalStorageUserStore.getToken expired");
- localStorage.removeItem(tokenkey);
- setTTL(tokenkey, null);
- return null;
- }
- var token = localStorage[tokenkey];
- authLogger.log("LocalStorageUserStore.getToken", token);
- return token;
- } catch(e) {
- authLogger.error("LocalStorageUserStore.getToken", e);
- return null;
- }
- },
- setToken: function(token, ttl) {
- if (token) {
- authLogger.log("LocalStorageUserStore.setToken", token, ttl);
- localStorage[tokenkey] = token;
- setTTL(tokenkey, ttl);
- } else {
- authLogger.log("LocalStorageUserStore.setToken", token, ttl, "deleting");
- localStorage.removeItem(tokenkey);
- setTTL(tokenkey, null);
- }
- }
- };
- };
-});
diff --git a/app/scripts/services/ws.js b/app/scripts/services/ws.js
deleted file mode 100644
index e65282ead7..0000000000
--- a/app/scripts/services/ws.js
+++ /dev/null
@@ -1,91 +0,0 @@
-'use strict';
-
-// Provide a websocket implementation that behaves like $http
-// Methods:
-// $ws({
-// url: "...", // required
-// method: "...", // defaults to WATCH
-// })
-// returns a promise to the opened WebSocket
-//
-// $ws.available()
-// returns true if WebSockets are available to use
-angular.module('openshiftConsole')
-.provider('$ws', function($httpProvider) {
-
- // $get method is called to build the $ws service
- this.$get = function($q, $injector, Logger) {
- var authLogger = Logger.get("auth");
- authLogger.log("$wsProvider.$get", arguments);
-
- // Build list of interceptors from $httpProvider when constructing the $ws service
- // Build in reverse-order, so the last interceptor added gets to handle the request first
- var _interceptors = [];
- angular.forEach($httpProvider.interceptors, function(interceptorFactory) {
- if (angular.isString(interceptorFactory)) {
- _interceptors.unshift($injector.get(interceptorFactory));
- } else {
- _interceptors.unshift($injector.invoke(interceptorFactory));
- }
- });
-
- // Implement $ws()
- var $ws = function(config) {
- config.method = angular.uppercase(config.method || "WATCH");
-
- authLogger.log("$ws (pre-intercept)", config.url.toString());
- var serverRequest = function(config) {
- authLogger.log("$ws (post-intercept)", config.url.toString());
- var ws = new WebSocket(config.url, config.protocols);
- if (config.onclose) { ws.onclose = config.onclose; }
- if (config.onmessage) { ws.onmessage = config.onmessage; }
- if (config.onopen) { ws.onopen = config.onopen; }
- if (config.onerror) { ws.onerror = config.onerror; }
- return ws;
- };
-
- // Apply interceptors to request config
- var chain = [serverRequest, undefined];
- var promise = $q.when(config);
- angular.forEach(_interceptors, function(interceptor) {
- if (interceptor.request || interceptor.requestError) {
- chain.unshift(interceptor.request, interceptor.requestError);
- }
- // TODO: figure out how to get interceptors to handle response errors from web sockets
- // if (interceptor.response || interceptor.responseError) {
- // chain.push(interceptor.response, interceptor.responseError);
- // }
- });
- while (chain.length) {
- var thenFn = chain.shift();
- var rejectFn = chain.shift();
- promise = promise.then(thenFn, rejectFn);
- }
- return promise;
- };
-
- // Implement $ws.available()
- $ws.available = function() {
- try {
- return !!WebSocket;
- }
- catch(e) {
- return false;
- }
- };
-
- return $ws;
- };
-})
-
-/* A WebSocket factory for kubernetesContainerTerminal */
-.factory("ContainerWebSocket", function(API_CFG, $ws) {
- return function AuthWebSocket(url, protocols) {
- var scheme;
- if (url.indexOf("/") === 0) {
- scheme = window.location.protocol === "http:" ? "ws://" : "wss://";
- url = scheme + API_CFG.openshift.hostPort + url;
- }
- return $ws({ url: url, method: "WATCH", protocols: protocols, auth: {} });
- };
-});
diff --git a/bower.json b/bower.json
index 1fa2c3f7a9..9113308430 100644
--- a/bower.json
+++ b/bower.json
@@ -46,7 +46,8 @@
"angular-moment": "1.0.0",
"angular-utf8-base64": "0.0.5",
"file-saver": "1.3.3",
- "bootstrap-switch": "3.3.3"
+ "bootstrap-switch": "3.3.3",
+ "origin-web-common": "0.0.3"
},
"devDependencies": {
"angular-mocks": "1.5.11",
diff --git a/dist/scripts/scripts.js b/dist/scripts/scripts.js
index 8aeec9269d..1c1efaf89e 100644
--- a/dist/scripts/scripts.js
+++ b/dist/scripts/scripts.js
@@ -1,9 +1,5 @@
"use strict";
-function ResourceGroupVersion(a, b, c) {
-return this.resource = a, this.group = b, this.version = c, this;
-}
-
window.OPENSHIFT_CONSTANTS = {
HELP_BASE_URL:"https://docs.openshift.org/latest/",
HELP:{
@@ -267,7 +263,7 @@ label:"Uncategorized",
description:""
} ]
} ]
-}, angular.module("openshiftConsole", [ "ngAnimate", "ngCookies", "ngResource", "ngRoute", "ngSanitize", "openshiftUI", "kubernetesUI", "registryUI.images", "ui.bootstrap", "patternfly.charts", "patternfly.sort", "openshiftConsoleTemplates", "ui.ace", "extension-registry", "as.sortable", "ui.select", "angular-inview", "angularMoment", "ab-base64" ]).config([ "$routeProvider", function(a) {
+}, angular.module("openshiftConsole", [ "ngAnimate", "ngCookies", "ngResource", "ngRoute", "ngSanitize", "openshiftUI", "kubernetesUI", "registryUI.images", "ui.bootstrap", "patternfly.charts", "patternfly.sort", "openshiftConsoleTemplates", "ui.ace", "extension-registry", "as.sortable", "ui.select", "angular-inview", "angularMoment", "ab-base64", "openshiftCommon" ]).config([ "$routeProvider", function(a) {
a.when("/", {
templateUrl:"views/projects.html",
controller:"ProjectsController"
@@ -554,7 +550,7 @@ redirectTo:"/project/:project/browse/rc/:rc"
}).otherwise({
redirectTo:"/"
});
-} ]).constant("API_CFG", _.get(window.OPENSHIFT_CONFIG, "api", {})).constant("APIS_CFG", _.get(window.OPENSHIFT_CONFIG, "apis", {})).constant("AUTH_CFG", _.get(window.OPENSHIFT_CONFIG, "auth", {})).constant("LOGGING_URL", _.get(window.OPENSHIFT_CONFIG, "loggingURL")).constant("METRICS_URL", _.get(window.OPENSHIFT_CONFIG, "metricsURL")).constant("LIMIT_REQUEST_OVERRIDES", _.get(window.OPENSHIFT_CONFIG, "limitRequestOverrides")).constant("BREAKPOINTS", {
+} ]).constant("LOGGING_URL", _.get(window.OPENSHIFT_CONFIG, "loggingURL")).constant("METRICS_URL", _.get(window.OPENSHIFT_CONFIG, "metricsURL")).constant("LIMIT_REQUEST_OVERRIDES", _.get(window.OPENSHIFT_CONFIG, "limitRequestOverrides")).constant("BREAKPOINTS", {
screenXsMin:480,
screenSmMin:768,
screenMdMin:992,
@@ -566,8 +562,8 @@ maxlength:253,
description:"Name must consist of lower-case letters, numbers, periods, and hyphens. It must start and end with a letter or a number."
}).constant("SOURCE_URL_PATTERN", /^[a-z][a-z0-9+.-@]*:(\/\/)?[0-9a-z_-]+/i).constant("RELATIVE_PATH_PATTERN", /^(?!\/)(?!\.\.(\/|$))(?!.*\/\.\.(\/|$)).*$/).constant("IS_IOS", /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream).constant("IS_SAFARI", /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)).constant("amTimeAgoConfig", {
titleFormat:"LLL"
-}).config([ "$httpProvider", "AuthServiceProvider", "RedirectLoginServiceProvider", "AUTH_CFG", "API_CFG", "kubernetesContainerSocketProvider", function(a, b, c, d, e, f) {
-a.interceptors.push("AuthInterceptor"), b.LoginService("RedirectLoginService"), b.LogoutService("DeleteTokenLogoutService"), b.UserStore("LocalStorageUserStore"), c.OAuthClientID(d.oauth_client_id), c.OAuthAuthorizeURI(d.oauth_authorize_uri), c.OAuthRedirectURI(URI(d.oauth_redirect_base).segment("oauth").toString()), f.WebSocketFactory = "ContainerWebSocket";
+}).config([ "kubernetesContainerSocketProvider", function(a) {
+a.WebSocketFactory = "ContainerWebSocket";
} ]).config([ "$compileProvider", function(a) {
a.aHrefSanitizationWhitelist(/^\s*(https?|mailto|git):/i);
} ]).run([ "$rootScope", "LabelFilter", function(a, b) {
@@ -583,87 +579,7 @@ return h ? b(e, null) || d :a(e, null, f, g) || d;
}, 1e3);
} ]).run([ "IS_IOS", function(a) {
a && $("body").addClass("ios");
-} ]), hawtioPluginLoader.addModule("openshiftConsole"), hawtioPluginLoader.registerPreBootstrapTask(function(a) {
-if (_.get(window, "OPENSHIFT_CONFIG.api.k8s.resources")) return void a();
-var b = {
-k8s:{},
-openshift:{}
-}, c = {}, d = [], e = window.location.protocol + "//", f = e + window.OPENSHIFT_CONFIG.api.k8s.hostPort + window.OPENSHIFT_CONFIG.api.k8s.prefix, g = $.get(f + "/v1").done(function(a) {
-b.k8s.v1 = _.indexBy(a.resources, "name");
-}).fail(function(a, b, c) {
-d.push({
-data:a,
-textStatus:b,
-xhr:c
-});
-}), h = e + window.OPENSHIFT_CONFIG.api.openshift.hostPort + window.OPENSHIFT_CONFIG.api.openshift.prefix, i = $.get(h + "/v1").done(function(a) {
-b.openshift.v1 = _.indexBy(a.resources, "name");
-}).fail(function(a, b, c) {
-d.push({
-data:a,
-textStatus:b,
-xhr:c
-});
-}), j = e + window.OPENSHIFT_CONFIG.apis.hostPort + window.OPENSHIFT_CONFIG.apis.prefix, k = $.get(j).then(function(a) {
-var b = [];
-return _.each(a.groups, function(a) {
-var e = {
-name:a.name,
-preferredVersion:a.preferredVersion.version,
-versions:{}
-};
-c[e.name] = e, _.each(a.versions, function(a) {
-var c = a.version;
-e.versions[c] = {
-version:c,
-groupVersion:a.groupVersion
-}, b.push($.get(j + "/" + a.groupVersion).done(function(a) {
-e.versions[c].resources = _.indexBy(a.resources, "name");
-}).fail(function(a, b, c) {
-d.push({
-data:a,
-textStatus:b,
-xhr:c
-});
-}));
-});
-}), $.when.apply(this, b);
-}, function(a, b, c) {
-d.push({
-data:a,
-textStatus:b,
-xhr:c
-});
-}), l = function() {
-window.OPENSHIFT_CONFIG.api.k8s.resources = b.k8s, window.OPENSHIFT_CONFIG.api.openshift.resources = b.openshift, window.OPENSHIFT_CONFIG.apis.groups = c, d.length && (window.OPENSHIFT_CONFIG.apis.API_DISCOVERY_ERRORS = d), a();
-};
-$.when(g, i, k).always(l);
-}), angular.module("openshiftConsole").provider("Logger", function() {
-this.$get = function() {
-var a = Logger.get("OpenShift"), b = {
-get:function(a) {
-var b = Logger.get("OpenShift/" + a), c = "OFF";
-return localStorage && (c = localStorage["OpenShiftLogLevel." + a] || c), b.setLevel(Logger[c]), b;
-},
-log:function() {
-a.log.apply(a, arguments);
-},
-info:function() {
-a.info.apply(a, arguments);
-},
-debug:function() {
-a.debug.apply(a, arguments);
-},
-warn:function() {
-a.warn.apply(a, arguments);
-},
-error:function() {
-a.error.apply(a, arguments);
-}
-}, c = "ERROR";
-return localStorage && (c = localStorage["OpenShiftLogLevel.main"] || c), a.setLevel(Logger[c]), b;
-};
-}), angular.module("openshiftConsole").factory("base64util", function() {
+} ]), hawtioPluginLoader.addModule("openshiftConsole"), angular.module("openshiftConsole").factory("base64util", function() {
return {
pad:function(a) {
if (!a) return "";
@@ -682,1012 +598,7 @@ return a;
}
}
};
-}), angular.module("openshiftConsole").provider("$ws", [ "$httpProvider", function(a) {
-this.$get = [ "$q", "$injector", "Logger", function(b, c, d) {
-var e = d.get("auth");
-e.log("$wsProvider.$get", arguments);
-var f = [];
-angular.forEach(a.interceptors, function(a) {
-angular.isString(a) ? f.unshift(c.get(a)) :f.unshift(c.invoke(a));
-});
-var g = function(a) {
-a.method = angular.uppercase(a.method || "WATCH"), e.log("$ws (pre-intercept)", a.url.toString());
-var c = function(a) {
-e.log("$ws (post-intercept)", a.url.toString());
-var b = new WebSocket(a.url, a.protocols);
-return a.onclose && (b.onclose = a.onclose), a.onmessage && (b.onmessage = a.onmessage), a.onopen && (b.onopen = a.onopen), a.onerror && (b.onerror = a.onerror), b;
-}, d = [ c, void 0 ], g = b.when(a);
-for (angular.forEach(f, function(a) {
-(a.request || a.requestError) && d.unshift(a.request, a.requestError);
-}); d.length; ) {
-var h = d.shift(), i = d.shift();
-g = g.then(h, i);
-}
-return g;
-};
-return g.available = function() {
-try {
-return !!WebSocket;
-} catch (a) {
-return !1;
-}
-}, g;
-} ];
-} ]).factory("ContainerWebSocket", [ "API_CFG", "$ws", function(a, b) {
-return function(c, d) {
-var e;
-return 0 === c.indexOf("/") && (e = "http:" === window.location.protocol ? "ws://" :"wss://", c = e + a.openshift.hostPort + c), b({
-url:c,
-method:"WATCH",
-protocols:d,
-auth:{}
-});
-};
-} ]), angular.module("openshiftConsole").provider("MemoryUserStore", function() {
-this.$get = [ "Logger", function(a) {
-var b = a.get("auth"), c = null, d = null;
-return {
-available:function() {
-return !0;
-},
-getUser:function() {
-return b.log("MemoryUserStore.getUser", c), c;
-},
-setUser:function(a, d) {
-b.log("MemoryUserStore.setUser", a), c = a;
-},
-getToken:function() {
-return b.log("MemoryUserStore.getToken", d), d;
-},
-setToken:function(a, c) {
-b.log("MemoryUserStore.setToken", a), d = a;
-}
-};
-} ];
-}).provider("SessionStorageUserStore", function() {
-this.$get = [ "Logger", function(a) {
-var b = a.get("auth"), c = "SessionStorageUserStore.user", d = "SessionStorageUserStore.token";
-return {
-available:function() {
-try {
-var a = String(new Date().getTime());
-sessionStorage["SessionStorageUserStore.test"] = a;
-var b = sessionStorage["SessionStorageUserStore.test"];
-return sessionStorage.removeItem("SessionStorageUserStore.test"), a === b;
-} catch (c) {
-return !1;
-}
-},
-getUser:function() {
-try {
-var a = JSON.parse(sessionStorage[c]);
-return b.log("SessionStorageUserStore.getUser", a), a;
-} catch (d) {
-return b.error("SessionStorageUserStore.getUser", d), null;
-}
-},
-setUser:function(a, d) {
-a ? (b.log("SessionStorageUserStore.setUser", a), sessionStorage[c] = JSON.stringify(a)) :(b.log("SessionStorageUserStore.setUser", a, "deleting"), sessionStorage.removeItem(c));
-},
-getToken:function() {
-try {
-var a = sessionStorage[d];
-return b.log("SessionStorageUserStore.getToken", a), a;
-} catch (c) {
-return b.error("SessionStorageUserStore.getToken", c), null;
-}
-},
-setToken:function(a, c) {
-a ? (b.log("SessionStorageUserStore.setToken", a), sessionStorage[d] = a) :(b.log("SessionStorageUserStore.setToken", a, "deleting"), sessionStorage.removeItem(d));
-}
-};
-} ];
-}).provider("LocalStorageUserStore", function() {
-this.$get = [ "Logger", function(a) {
-var b = a.get("auth"), c = "LocalStorageUserStore.user", d = "LocalStorageUserStore.token", e = function(a) {
-return a + ".ttl";
-}, f = function(a, c) {
-if (c) {
-var d = new Date().getTime() + 1e3 * c;
-localStorage[e(a)] = d, b.log("LocalStorageUserStore.setTTL", a, c, new Date(d).toString());
-} else localStorage.removeItem(e(a)), b.log("LocalStorageUserStore.setTTL deleting", a);
-}, g = function(a) {
-var c = localStorage[e(a)];
-if (!c) return !1;
-var d = parseInt(c) < new Date().getTime();
-return b.log("LocalStorageUserStore.isTTLExpired", a, d), d;
-};
-return {
-available:function() {
-try {
-var a = String(new Date().getTime());
-localStorage["LocalStorageUserStore.test"] = a;
-var b = localStorage["LocalStorageUserStore.test"];
-return localStorage.removeItem("LocalStorageUserStore.test"), a === b;
-} catch (c) {
-return !1;
-}
-},
-getUser:function() {
-try {
-if (g(c)) return b.log("LocalStorageUserStore.getUser expired"), localStorage.removeItem(c), f(c, null), null;
-var a = JSON.parse(localStorage[c]);
-return b.log("LocalStorageUserStore.getUser", a), a;
-} catch (d) {
-return b.error("LocalStorageUserStore.getUser", d), null;
-}
-},
-setUser:function(a, d) {
-a ? (b.log("LocalStorageUserStore.setUser", a, d), localStorage[c] = JSON.stringify(a), f(c, d)) :(b.log("LocalStorageUserStore.setUser", a, "deleting"), localStorage.removeItem(c), f(c, null));
-},
-getToken:function() {
-try {
-if (g(d)) return b.log("LocalStorageUserStore.getToken expired"), localStorage.removeItem(d), f(d, null), null;
-var a = localStorage[d];
-return b.log("LocalStorageUserStore.getToken", a), a;
-} catch (c) {
-return b.error("LocalStorageUserStore.getToken", c), null;
-}
-},
-setToken:function(a, c) {
-a ? (b.log("LocalStorageUserStore.setToken", a, c), localStorage[d] = a, f(d, c)) :(b.log("LocalStorageUserStore.setToken", a, c, "deleting"), localStorage.removeItem(d), f(d, null));
-}
-};
-} ];
-}), ResourceGroupVersion.prototype.toString = function() {
-var a = this.resource;
-return this.group && (a += "/" + this.group), this.version && (a += "/" + this.version), a;
-}, ResourceGroupVersion.prototype.primaryResource = function() {
-if (!this.resource) return "";
-var a = this.resource.indexOf("/");
-return a === -1 ? this.resource :this.resource.substring(0, a);
-}, ResourceGroupVersion.prototype.subresources = function() {
-var a = (this.resource || "").split("/");
-return a.shift(), a;
-}, ResourceGroupVersion.prototype.equals = function(a, b, c) {
-return this.resource === a && (1 === arguments.length || this.group === b && (2 === arguments.length || this.version === c));
-}, angular.module("openshiftConsole").factory("APIService", [ "API_CFG", "APIS_CFG", "AuthService", "Constants", "Logger", "$q", "$http", "$filter", "$window", function(a, b, c, d, e, f, g, h, i) {
-function j(a) {
-if (!a) return a;
-var b = a.indexOf("/");
-return b === -1 ? a.toLowerCase() :a.substring(0, b).toLowerCase() + a.substring(b);
-}
-function k(a, b) {
-if (!a) return "";
-var c = a;
-if (b) {
-var d = h("humanizeKind");
-c = d(c);
-}
-return c = String(c).toLowerCase(), "endpoints" === c || "securitycontextconstraints" === c || ("s" === c[c.length - 1] ? c += "es" :"y" === c[c.length - 1] ? c = c.substring(0, c.length - 1) + "ies" :c += "s"), c;
-}
-var l = {
-"":"v1",
-extensions:"v1beta1"
-}, m = function(a) {
-if (a instanceof ResourceGroupVersion) return a;
-var c, d, e;
-return angular.isString(a) ? (c = j(a), d = "", e = l[d]) :a && a.resource && (c = j(a.resource), d = a.group || "", e = a.version || l[d] || _.get(b, [ "groups", d, "preferredVersion" ])), new ResourceGroupVersion(c, d, e);
-}, n = function(a) {
-if (a) {
-var b = a.split("/");
-return 1 === b.length ? "v1" === b[0] ? {
-group:"",
-version:b[0]
-} :{
-group:b[0],
-version:""
-} :2 === b.length ? {
-group:b[0],
-version:b[1]
-} :void e.warn('Invalid apiVersion "' + a + '"');
-}
-}, o = function(a) {
-if (a && a.kind && a.apiVersion) {
-var b = k(a.kind);
-if (b) {
-var c = n(a.apiVersion);
-if (c) return new ResourceGroupVersion(b, c.group, c.version);
-}
-}
-}, p = function(a, b) {
-if (a && b) {
-var c = k(b.kind), d = n(b.apiVersion), e = m(a);
-if (c && d && e) return angular.isString(a) ? (e.equals(c) && (e.group = d.group, e.version = d.version), e) :(e.equals(c, d.group) && (e.version = d.version), e);
-}
-}, q = function(d) {
-if (b.API_DISCOVERY_ERRORS) {
-var e = _.every(b.API_DISCOVERY_ERRORS, function(a) {
-return 0 === _.get(a, "data.status");
-});
-return e && !c.isLoggedIn() ? void c.withUser() :void (i.location.href = URI("error").query({
-error_description:"Unable to load details about the server. If the problem continues, please contact your system administrator.",
-error:"API_DISCOVERY"
-}).toString());
-}
-d = m(d);
-var f = d.primaryResource();
-if (d.group) {
-if (!_.get(b, [ "groups", d.group, "versions", d.version, "resources", f ])) return;
-return {
-hostPort:b.hostPort,
-prefix:b.prefix,
-group:d.group,
-version:d.version
-};
-}
-var g;
-for (var h in a) if (g = a[h], _.get(g, [ "resources", d.version, f ])) return {
-hostPort:g.hostPort,
-prefix:g.prefix,
-version:d.version
-};
-}, r = function(a) {
-var b = "", c = "";
-return a && a.kind && (b = a.kind), a && a.apiVersion && (c = a.apiVersion), "Invalid kind (" + b + ") or API version (" + c + ")";
-}, s = function(a) {
-var b = "", c = "";
-return a && a.kind && (b = a.kind), a && a.apiVersion && (c = a.apiVersion), "The API version " + c + " for kind " + b + " is not supported by this server";
-}, t = function(c) {
-var e = [], f = d.AVAILABLE_KINDS_BLACKLIST;
-return _.each(a, function(a) {
-_.each(a.resources.v1, function(a) {
-if (a.namespaced || c) {
-if (a.name.indexOf("/") >= 0 || _.contains(f, a.kind)) return;
-e.push({
-kind:a.kind
-});
-}
-});
-}), _.each(b.groups, function(a) {
-var b = l[a.name] || a.preferredVersion;
-_.each(a.versions[b].resources, function(b) {
-b.name.indexOf("/") >= 0 || _.contains(f, b.kind) || "autoscaling" === a.name && "HorizontalPodAutoscaler" === b.kind || "batch" === a.name && "Job" === b.kind || (b.namespaced || c) && e.push({
-kind:b.kind,
-group:a.name
-});
-});
-}), _.uniq(e, !1, function(a) {
-return a.group + "/" + a.kind;
-});
-}, u = t(!1), v = t(!0), w = function(a) {
-return a ? v :u;
-};
-return {
-toResourceGroupVersion:m,
-parseGroupVersion:n,
-objectToResourceGroupVersion:o,
-deriveTargetResource:p,
-kindToResource:k,
-apiInfo:q,
-invalidObjectKindOrVersion:r,
-unsupportedObjectKindOrVersion:s,
-availableKinds:w
-};
-} ]), angular.module("openshiftConsole").provider("AuthService", function() {
-var a = "";
-this.UserStore = function(b) {
-return b && (a = b), a;
-};
-var b = "";
-this.LoginService = function(a) {
-return a && (b = a), b;
-};
-var c = "";
-this.LogoutService = function(a) {
-return a && (c = a), c;
-};
-var d = function(a, b, c) {
-if (b) return angular.isString(b) ? a.get(b) :a.invoke(b);
-throw c + " not set";
-};
-this.$get = [ "$q", "$injector", "$log", "$rootScope", "Logger", function(e, f, g, h, i) {
-var j = i.get("auth");
-j.log("AuthServiceProvider.$get", arguments);
-var k = $.Callbacks(), l = $.Callbacks(), m = $.Callbacks(), n = null, o = null, p = d(f, a, "AuthServiceProvider.UserStore()");
-p.available() || i.error("AuthServiceProvider.$get user store " + a + " not available");
-var q = d(f, b, "AuthServiceProvider.LoginService()"), r = d(f, c, "AuthServiceProvider.LogoutService()");
-return {
-UserStore:function() {
-return p;
-},
-isLoggedIn:function() {
-return !!p.getUser();
-},
-withUser:function() {
-var a = p.getUser();
-return a ? (h.user = a, j.log("AuthService.withUser()", a), e.when(a)) :(j.log("AuthService.withUser(), calling startLogin()"), this.startLogin());
-},
-setUser:function(a, b, c) {
-j.log("AuthService.setUser()", a, b, c);
-var d = p.getUser();
-p.setUser(a, c), p.setToken(b, c), h.user = a;
-var e = d && d.metadata && d.metadata.name, f = a && a.metadata && a.metadata.name;
-e !== f && (j.log("AuthService.setUser(), user changed", d, a), m.fire(a));
-},
-requestRequiresAuth:function(a) {
-var b = !!a.auth;
-return j.log("AuthService.requestRequiresAuth()", a.url.toString(), b), b;
-},
-addAuthToRequest:function(a) {
-var b = "";
-return a && a.auth && a.auth.token ? (b = a.auth.token, j.log("AuthService.addAuthToRequest(), using token from request config", b)) :(b = p.getToken(), j.log("AuthService.addAuthToRequest(), using token from user store", b)), b ? ("WATCH" === a.method ? (a.url = URI(a.url).addQuery({
-access_token:b
-}).toString(), j.log("AuthService.addAuthToRequest(), added token param", a.url)) :(a.headers.Authorization = "Bearer " + b, j.log("AuthService.addAuthToRequest(), added token header", a.headers.Authorization)), !0) :(j.log("AuthService.addAuthToRequest(), no token available"), !1);
-},
-startLogin:function() {
-if (n) return j.log("Login already in progress"), n;
-var a = this;
-return n = q.login().then(function(b) {
-a.setUser(b.user, b.token, b.ttl), k.fire(b.user);
-})["catch"](function(a) {
-i.error(a);
-})["finally"](function() {
-n = null;
-});
-},
-startLogout:function() {
-if (o) return j.log("Logout already in progress"), o;
-var a = this, b = p.getUser(), c = p.getToken(), d = this.isLoggedIn();
-return o = r.logout(b, c).then(function() {
-j.log("Logout service success");
-})["catch"](function(a) {
-j.error("Logout service error", a);
-})["finally"](function() {
-a.setUser(null, null);
-var b = a.isLoggedIn();
-d && !b && l.fire(), o = null;
-});
-},
-onLogin:function(a) {
-k.add(a);
-},
-onLogout:function(a) {
-l.add(a);
-},
-onUserChanged:function(a) {
-m.add(a);
-}
-};
-} ];
-}).factory("AuthInterceptor", [ "$q", "AuthService", function(a, b) {
-var c = [];
-return {
-request:function(d) {
-if (!b.requestRequiresAuth(d)) return d;
-if (b.addAuthToRequest(d)) return d;
-if (d.auth && d.auth.triggerLogin === !1) return d;
-var e = a.defer();
-return c.push([ e, d, "request" ]), b.startLogin(), e.promise;
-},
-responseError:function(d) {
-var e = d.config.auth || {};
-if (!b.requestRequiresAuth(d.config)) return a.reject(d);
-if (e.triggerLogin === !1) return a.reject(d);
-var f = d.status;
-switch (f) {
-case 401:
-var g = a.defer();
-return c.push([ g, d.config, "responseError" ]), b.startLogin(), g.promise;
-
-default:
-return a.reject(d);
-}
-}
-};
-} ]), angular.module("openshiftConsole").factory("AuthorizationService", [ "$q", "$cacheFactory", "Logger", "$interval", "APIService", "DataService", function(a, b, c, d, e, f) {
-var g = null, h = b("rulesCache", {
-number:10
-}), i = !1, j = [ "localresourceaccessreviews", "localsubjectaccessreviews", "resourceaccessreviews", "selfsubjectrulesreviews", "subjectaccessreviews" ], k = function(a) {
-var b = {};
-return _.each(a, function(a) {
-_.each(a.apiGroups, function(c) {
-b[c] || (b[c] = {}), _.each(a.resources, function(d) {
-b[c][d] = a.verbs;
-});
-});
-}), b;
-}, l = function(a) {
-return "projectrequests" !== a && !_.contains(a, "/") && !_.contains(j, a);
-}, m = function(a) {
-return _.some(a, function(a) {
-return _.some(a.resources, function(b) {
-return l(b) && !_.isEmpty(_.intersection(a.verbs, [ "*", "create", "update" ]));
-});
-});
-}, n = function(b, d) {
-var j = a.defer();
-g = b;
-var l = h.get(b), n = "selfsubjectrulesreviews";
-if (!l || l.forceRefresh || d) if (e.apiInfo(n)) {
-c.log("AuthorizationService, loading user rules for " + b + " project");
-var o = {
-kind:"SelfSubjectRulesReview",
-apiVersion:"v1"
-};
-f.create(n, null, o, {
-namespace:b
-}).then(function(a) {
-var c = k(a.status.rules), d = m(a.status.rules);
-h.put(b, {
-rules:c,
-canAddToProject:d,
-forceRefresh:!1,
-cacheTimestamp:_.now()
-}), j.resolve();
-}, function() {
-i = !0, j.resolve();
-});
-} else c.log("AuthorizationService, resource 'selfsubjectrulesreviews' is not part of APIserver. Switching into permissive mode."), i = !0, j.resolve(); else c.log("AuthorizationService, using cached rules for " + b + " project"), _.now() - l.cacheTimestamp >= 6e5 && (l.forceRefresh = !0), j.resolve();
-return j.promise;
-}, o = function(a) {
-return _.get(h.get(a || g), [ "rules" ]);
-}, p = function(a, b, c, d) {
-var e = a[c];
-if (!e) return !1;
-var f = e[d];
-return !!f && (_.contains(f, b) || _.contains(f, "*"));
-}, q = function(a, b, c) {
-if (i) return !0;
-var d = e.toResourceGroupVersion(a), f = o(c || g);
-return !!f && (p(f, b, d.group, d.resource) || p(f, b, "*", "*") || p(f, b, d.group, "*") || p(f, b, "*", d.resource));
-}, r = function(a) {
-return !!i || !!_.get(h.get(a || g), [ "canAddToProject" ]);
-};
-return {
-checkResource:l,
-getProjectRules:n,
-canI:q,
-canIAddToProject:r,
-getRulesForProject:o
-};
-} ]), angular.module("openshiftConsole").factory("DataService", [ "$cacheFactory", "$http", "$ws", "$rootScope", "$q", "API_CFG", "APIService", "Notification", "Logger", "$timeout", "base64", "base64util", function(a, b, c, d, e, f, g, h, i, j, k, l) {
-function m(a) {
-this._data = {}, this._objectsByAttribute(a, "metadata.name", this._data);
-}
-function n(a, b, c, d) {
-for (var e = b.split("."), f = a, g = 0; g < e.length; g++) if (f = f[e[g]], void 0 === f) return;
-if ($.isArray(f)) ; else if ($.isPlainObject(f)) for (var h in f) {
-var i = f[h];
-c[h] || (c[h] = {}), "DELETED" === d ? delete c[h][i] :c[h][i] = a;
-} else "DELETED" === d ? delete c[f] :c[f] = a;
-}
-function o() {
-this._listDeferredMap = {}, this._watchCallbacksMap = {}, this._watchObjectCallbacksMap = {}, this._watchOperationMap = {}, this._listOperationMap = {}, this._resourceVersionMap = {}, this._dataCache = a("dataCache", {
-number:25
-}), this._immutableDataCache = a("immutableDataCache", {
-number:50
-}), this._watchOptionsMap = {}, this._watchWebsocketsMap = {}, this._watchPollTimeoutsMap = {}, this._websocketEventsMap = {};
-var b = this;
-d.$on("$routeChangeStart", function(a, c, d) {
-b._websocketEventsMap = {};
-});
-}
-function p(a) {
-var b = 3e4;
-return a.length >= r && Date.now() - a[0].time < b;
-}
-function q(a) {
-var b = 5;
-if (a.length < b) return !1;
-for (var c = a.length - b; c < a.length; c++) if ("close" !== a[c].type) return !1;
-return !0;
-}
-m.prototype.by = function(a) {
-if ("metadata.name" === a) return this._data;
-var b = {};
-for (var c in this._data) n(this._data[c], a, b, null);
-return b;
-}, m.prototype.update = function(a, b) {
-n(a, "metadata.name", this._data, b);
-}, m.prototype._objectsByAttribute = function(a, b, c, d) {
-angular.forEach(a, function(a, e) {
-n(a, b, c, d ? d[e] :null);
-});
-}, o.prototype.list = function(a, b, c, d) {
-a = g.toResourceGroupVersion(a);
-var e = this._uniqueKey(a, null, b, _.get(d, "http.params")), f = this._listDeferred(e);
-return c && f.promise.then(c), this._isCached(e) ? f.resolve(this._data(e)) :this._listInFlight(e) || this._startListOp(a, b, d), f.promise;
-}, o.prototype["delete"] = function(a, c, d, f) {
-a = g.toResourceGroupVersion(a), f = f || {};
-var h, i = e.defer(), j = this, k = {};
-return _.has(f, "gracePeriodSeconds") && (h = {
-kind:"DeleteOptions",
-apiVersion:"v1",
-gracePeriodSeconds:f.gracePeriodSeconds
-}, k["Content-Type"] = "application/json"), this._getNamespace(a, d, f).then(function(e) {
-b(angular.extend({
-method:"DELETE",
-auth:{},
-data:h,
-headers:k,
-url:j._urlForResource(a, c, d, !1, e)
-}, f.http || {})).success(function(a, b, c, d, e) {
-i.resolve(a);
-}).error(function(a, b, c, d) {
-i.reject({
-data:a,
-status:b,
-headers:c,
-config:d
-});
-});
-}), i.promise;
-}, o.prototype.update = function(a, c, d, f, h) {
-a = g.deriveTargetResource(a, d), h = h || {};
-var i = e.defer(), j = this;
-return this._getNamespace(a, f, h).then(function(e) {
-b(angular.extend({
-method:"PUT",
-auth:{},
-data:d,
-url:j._urlForResource(a, c, f, !1, e)
-}, h.http || {})).success(function(a, b, c, d, e) {
-i.resolve(a);
-}).error(function(a, b, c, d) {
-i.reject({
-data:a,
-status:b,
-headers:c,
-config:d
-});
-});
-}), i.promise;
-}, o.prototype.create = function(a, c, d, f, h) {
-a = g.deriveTargetResource(a, d), h = h || {};
-var i = e.defer(), j = this;
-return this._getNamespace(a, f, h).then(function(e) {
-b(angular.extend({
-method:"POST",
-auth:{},
-data:d,
-url:j._urlForResource(a, c, f, !1, e)
-}, h.http || {})).success(function(a, b, c, d, e) {
-i.resolve(a);
-}).error(function(a, b, c, d) {
-i.reject({
-data:a,
-status:b,
-headers:c,
-config:d
-});
-});
-}), i.promise;
-}, o.prototype.batch = function(a, b, c, d) {
-function f() {
-0 === l && h.resolve({
-success:i,
-failure:j
-});
-}
-var h = e.defer(), i = [], j = [], k = this, l = a.length;
-return c = c || "create", _.each(a, function(a) {
-var e = g.objectToResourceGroupVersion(a);
-if (!e) return j.push({
-object:a,
-data:{
-message:g.invalidObjectKindOrVersion(a)
-}
-}), l--, void f();
-if (!g.apiInfo(e)) return j.push({
-object:a,
-data:{
-message:g.unsupportedObjectKindOrVersion(a)
-}
-}), l--, void f();
-var m = function(b) {
-b.object = a, i.push(b), l--, f();
-}, n = function(b) {
-b.object = a, j.push(b), l--, f();
-};
-switch (c) {
-case "create":
-k.create(e, null, a, b, d).then(m, n);
-break;
-
-case "update":
-k.update(e, a.metadata.name, a, b, d).then(m, n);
-break;
-
-default:
-return h.reject({
-data:"Invalid '" + c + "' action.",
-status:400,
-headers:function() {
-return null;
-},
-config:{},
-object:a
-});
-}
-}), h.promise;
-}, o.prototype.get = function(a, c, d, f) {
-a = g.toResourceGroupVersion(a), f = f || {};
-var i = this._uniqueKey(a, c, d, _.get(f, "http.params"));
-!!f.force;
-delete f.force;
-var k = e.defer(), l = this._immutableData(i);
-if (this._hasImmutable(a, l, c)) j(function() {
-k.resolve(l.by("metadata.name")[c]);
-}, 0); else {
-var m = this;
-this._getNamespace(a, d, f).then(function(e) {
-b(angular.extend({
-method:"GET",
-auth:{},
-url:m._urlForResource(a, c, d, !1, e)
-}, f.http || {})).success(function(b, c, d, e, f) {
-m._isImmutable(a) && (l ? l.update(b, "ADDED") :m._immutableData(i, [ b ])), k.resolve(b);
-}).error(function(b, d, e, g) {
-if (f.errorNotification !== !1) {
-var i = "Failed to get " + a + "/" + c;
-0 !== d && (i += " (" + d + ")"), h.error(i);
-}
-k.reject({
-data:b,
-status:d,
-headers:e,
-config:g
-});
-});
-});
-}
-return k.promise;
-}, o.prototype.createStream = function(a, b, d, e, f) {
-var h = this;
-a = g.toResourceGroupVersion(a);
-var j, m = f ? "binary.k8s.io" :"base64.binary.k8s.io", n = "stream_", o = {}, p = {}, q = {}, r = {}, s = function() {
-return h._getNamespace(a, d, {}).then(function(g) {
-var j = 0;
-return c({
-url:h._urlForResource(a, b, d, !0, _.extend(g, e)),
-auth:{},
-onopen:function(a) {
-_.each(o, function(b) {
-b(a);
-});
-},
-onmessage:function(a) {
-if (!_.isString(a.data)) return void i.log("log stream response is not a string", a.data);
-var b;
-f || (b = k.decode(l.pad(a.data)), j += b.length), _.each(p, function(c) {
-f ? c(a.data) :c(b, a.data, j);
-});
-},
-onclose:function(a) {
-_.each(q, function(b) {
-b(a);
-});
-},
-onerror:function(a) {
-_.each(r, function(b) {
-b(a);
-});
-},
-protocols:m
-}).then(function(a) {
-return i.log("Streaming pod log", a), a;
-});
-});
-};
-return {
-onOpen:function(a) {
-if (_.isFunction(a)) {
-var b = _.uniqueId(n);
-return o[b] = a, b;
-}
-},
-onMessage:function(a) {
-if (_.isFunction(a)) {
-var b = _.uniqueId(n);
-return p[b] = a, b;
-}
-},
-onClose:function(a) {
-if (_.isFunction(a)) {
-var b = _.uniqueId(n);
-return q[b] = a, b;
-}
-},
-onError:function(a) {
-if (_.isFunction(a)) {
-var b = _.uniqueId(n);
-return r[b] = a, b;
-}
-},
-remove:function(a) {
-o[a] && delete o[a], p[a] && delete p[a], q[a] && delete q[a], r[a] && delete r[a];
-},
-start:function() {
-return j = s();
-},
-stop:function() {
-j.then(function(a) {
-a.close();
-});
-}
-};
-}, o.prototype.watch = function(a, b, c, d) {
-a = g.toResourceGroupVersion(a), d = d || {};
-var e = this._uniqueKey(a, null, b, _.get(d, "http.params"));
-if (c) this._watchCallbacks(e).add(c); else if (!this._watchCallbacks(e).has()) return {};
-var f = this._watchOptions(e);
-if (f) {
-if (!!f.poll != !!d.poll) throw "A watch already exists for " + a + " with a different polling option.";
-} else this._watchOptions(e, d);
-var h = this;
-if (this._isCached(e)) c && j(function() {
-c(h._data(e));
-}, 0); else {
-if (c) {
-var i = this._resourceVersion(e);
-this._data(e) && j(function() {
-i === h._resourceVersion(e) && c(h._data(e));
-}, 0);
-}
-this._listInFlight(e) || this._startListOp(a, b, d);
-}
-return {
-resource:a,
-context:b,
-callback:c,
-opts:d
-};
-}, o.prototype.watchObject = function(a, b, c, d, e) {
-a = g.toResourceGroupVersion(a), e = e || {};
-var f, h = this._uniqueKey(a, b, c, _.get(e, "http.params"));
-if (d) {
-this._watchObjectCallbacks(h).add(d);
-var i = this;
-f = function(a, c, d) {
-if (d && d.metadata.name === b) i._watchObjectCallbacks(h).fire(d, c); else if (!d) {
-var e = a.by("metadata.name");
-e[b] && i._watchObjectCallbacks(h).fire(e[b]);
-}
-};
-} else if (!this._watchObjectCallbacks(h).has()) return {};
-var j = this.watch(a, c, f, e);
-return j.objectCallback = d, j.objectName = b, j;
-}, o.prototype.unwatch = function(a) {
-var b = a.resource, c = a.objectName, d = a.context, e = a.callback, f = a.objectCallback, g = a.opts, h = this._uniqueKey(b, null, d, _.get(g, "http.params"));
-if (f && c) {
-var i = this._uniqueKey(b, c, d, _.get(g, "http.params")), j = this._watchObjectCallbacks(i);
-j.remove(f);
-}
-var k = this._watchCallbacks(h);
-if (e && k.remove(e), !k.has()) {
-if (g && g.poll) clearTimeout(this._watchPollTimeouts(h)), this._watchPollTimeouts(h, null); else if (this._watchWebsockets(h)) {
-var l = this._watchWebsockets(h);
-l.shouldClose = !0, l.close(), this._watchWebsockets(h, null);
-}
-this._watchInFlight(h, !1), this._watchOptions(h, null);
-}
-}, o.prototype.unwatchAll = function(a) {
-for (var b = 0; b < a.length; b++) this.unwatch(a[b]);
-}, o.prototype._watchCallbacks = function(a) {
-return this._watchCallbacksMap[a] || (this._watchCallbacksMap[a] = $.Callbacks()), this._watchCallbacksMap[a];
-}, o.prototype._watchObjectCallbacks = function(a) {
-return this._watchObjectCallbacksMap[a] || (this._watchObjectCallbacksMap[a] = $.Callbacks()), this._watchObjectCallbacksMap[a];
-}, o.prototype._listDeferred = function(a) {
-return this._listDeferredMap[a] || (this._listDeferredMap[a] = e.defer()), this._listDeferredMap[a];
-}, o.prototype._watchInFlight = function(a, b) {
-return b || b === !1 ? void (this._watchOperationMap[a] = b) :this._watchOperationMap[a];
-}, o.prototype._listInFlight = function(a, b) {
-return b || b === !1 ? void (this._listOperationMap[a] = b) :this._listOperationMap[a];
-}, o.prototype._resourceVersion = function(a, b) {
-return b ? void (this._resourceVersionMap[a] = b) :this._resourceVersionMap[a];
-}, o.prototype._data = function(a, b) {
-return b ? this._dataCache.put(a, new m(b)) :this._dataCache.get(a);
-}, o.prototype._immutableData = function(a, b) {
-return b ? this._immutableDataCache.put(a, new m(b)) :this._immutableDataCache.get(a);
-}, o.prototype._isCached = function(a) {
-return this._watchInFlight(a) && this._resourceVersion(a) && !!this._data(a);
-}, o.prototype._watchOptions = function(a, b) {
-return void 0 === b ? this._watchOptionsMap[a] :void (this._watchOptionsMap[a] = b);
-}, o.prototype._watchPollTimeouts = function(a, b) {
-return b ? void (this._watchPollTimeoutsMap[a] = b) :this._watchPollTimeoutsMap[a];
-}, o.prototype._watchWebsockets = function(a, b) {
-return b ? void (this._watchWebsocketsMap[a] = b) :this._watchWebsocketsMap[a];
-};
-var r = 10;
-o.prototype._addWebsocketEvent = function(a, b) {
-var c = this._websocketEventsMap[a];
-for (c || (c = this._websocketEventsMap[a] = []), c.push({
-type:b,
-time:Date.now()
-}); c.length > r; ) c.shift();
-}, o.prototype._isTooManyWebsocketRetries = function(a) {
-var b = this._websocketEventsMap[a];
-return !!b && (p(b) ? (i.log("Too many websocket open or close events for resource/context in a short period", a, b), !0) :!!q(b) && (i.log("Too many consecutive websocket close events for resource/context", a, b), !0));
-};
-var s = function(a) {
-var b = _.keysIn(_.pick(a, [ "fieldSelector", "labelSelector" ])).sort();
-return _.reduce(b, function(c, d, e) {
-return c + d + "=" + encodeURIComponent(a[d]) + (e < b.length - 1 ? "&" :"");
-}, "?");
-};
-o.prototype._uniqueKey = function(a, b, c, d) {
-var e = c && c.namespace || _.get(c, "project.metadata.name") || c.projectName;
-return this._urlForResource(a, b, c, null, angular.extend({}, {}, {
-namespace:e
-})).toString() + s(d || {});
-}, o.prototype._startListOp = function(a, c, d) {
-d = d || {};
-var e = this._uniqueKey(a, null, c, _.get(d, "http.params"));
-this._listInFlight(e, !0);
-var f = this;
-c.projectPromise && !a.equals("projects") ? c.projectPromise.done(function(g) {
-b(angular.extend({
-method:"GET",
-auth:{},
-url:f._urlForResource(a, null, c, !1, {
-namespace:g.metadata.name
-})
-}, d.http || {})).success(function(b, g, h, i, j) {
-f._listOpComplete(e, a, c, d, b);
-}).error(function(b, c, g, i) {
-f._listInFlight(e, !1);
-var j = f._listDeferred(e);
-if (delete f._listDeferredMap[e], j.reject(b, c, g, i), _.get(d, "errorNotification", !0)) {
-var k = "Failed to list " + a;
-0 !== c && (k += " (" + c + ")"), h.error(k);
-}
-});
-}) :b({
-method:"GET",
-auth:{},
-url:this._urlForResource(a, null, c)
-}).success(function(b, g, h, i, j) {
-f._listOpComplete(e, a, c, d, b);
-}).error(function(b, c, g, i) {
-f._listInFlight(e, !1);
-var j = f._listDeferred(e);
-if (delete f._listDeferredMap[e], j.reject(b, c, g, i), _.get(d, "errorNotification", !0)) {
-var k = "Failed to list " + a;
-0 !== c && (k += " (" + c + ")"), h.error(k);
-}
-});
-}, o.prototype._listOpComplete = function(a, b, c, d, e) {
-e.items || console.warn("List request for " + b + " returned a null items array. This is an invalid API response.");
-var f = e.items || [];
-e.kind && e.kind.indexOf("List") === e.kind.length - 4 && angular.forEach(f, function(a) {
-a.kind || (a.kind = e.kind.slice(0, -4)), a.apiVersion || (a.apiVersion = e.apiVersion);
-}), this._listInFlight(a, !1);
-var g = this._listDeferred(a);
-if (delete this._listDeferredMap[a], this._resourceVersion(a, e.resourceVersion || e.metadata.resourceVersion), this._data(a, f), g.resolve(this._data(a)), this._watchCallbacks(a).fire(this._data(a)), this._watchCallbacks(a).has()) {
-var h = this._watchOptions(a) || {};
-h.poll ? (this._watchInFlight(a, !0), this._watchPollTimeouts(a, setTimeout($.proxy(this, "_startListOp", b, c), h.pollInterval || 5e3))) :this._watchInFlight(a) || this._startWatchOp(a, b, c, d, this._resourceVersion(a));
-}
-}, o.prototype._startWatchOp = function(a, b, d, e, f) {
-if (this._watchInFlight(a, !0), c.available()) {
-var g = this, h = _.get(e, "http.params") || {};
-h.watch = !0, f && (h.resourceVersion = f), d.projectPromise && !b.equals("projects") ? d.projectPromise.done(function(f) {
-h.namespace = f.metadata.name, c({
-method:"WATCH",
-url:g._urlForResource(b, null, d, !0, h),
-auth:{},
-onclose:$.proxy(g, "_watchOpOnClose", b, d, e),
-onmessage:$.proxy(g, "_watchOpOnMessage", b, d, e),
-onopen:$.proxy(g, "_watchOpOnOpen", b, d, e)
-}).then(function(b) {
-i.log("Watching", b), g._watchWebsockets(a, b);
-});
-}) :c({
-method:"WATCH",
-url:g._urlForResource(b, null, d, !0, h),
-auth:{},
-onclose:$.proxy(g, "_watchOpOnClose", b, d, e),
-onmessage:$.proxy(g, "_watchOpOnMessage", b, d, e),
-onopen:$.proxy(g, "_watchOpOnOpen", b, d, e)
-}).then(function(b) {
-i.log("Watching", b), g._watchWebsockets(a, b);
-});
-}
-}, o.prototype._watchOpOnOpen = function(a, b, c, d) {
-i.log("Websocket opened for resource/context", a, b);
-var e = this._uniqueKey(a, null, b, _.get(c, "http.params"));
-this._addWebsocketEvent(e, "open");
-}, o.prototype._watchOpOnMessage = function(a, b, c, d) {
-var e = this._uniqueKey(a, null, b, _.get(c, "http.params"));
-try {
-var f = $.parseJSON(d.data);
-if ("ERROR" == f.type) return i.log("Watch window expired for resource/context", a, b), void (d.target && (d.target.shouldRelist = !0));
-"DELETED" === f.type && f.object && f.object.metadata && !f.object.metadata.deletionTimestamp && (f.object.metadata.deletionTimestamp = new Date().toISOString()), f.object && this._resourceVersion(e, f.object.resourceVersion || f.object.metadata.resourceVersion), this._data(e).update(f.object, f.type);
-var g = this;
-j(function() {
-g._watchCallbacks(e).fire(g._data(e), f.type, f.object);
-}, 0);
-} catch (h) {
-i.error("Error processing message", a, d.data);
-}
-}, o.prototype._watchOpOnClose = function(a, b, c, d) {
-var e = d.target, f = this._uniqueKey(a, null, b, _.get(c, "http.params"));
-if (!e) return void i.log("Skipping reopen, no eventWS in event", d);
-var g = this._watchWebsockets(f);
-if (!g) return void i.log("Skipping reopen, no registeredWS for resource/context", a, b);
-if (e !== g) return void i.log("Skipping reopen, eventWS does not match registeredWS", e, g);
-if (this._watchInFlight(f, !1), e.shouldClose) return void i.log("Skipping reopen, eventWS was explicitly closed", e);
-if (d.wasClean) return void i.log("Skipping reopen, clean close", d);
-if (!this._watchCallbacks(f).has()) return void i.log("Skipping reopen, no listeners registered for resource/context", a, b);
-if (this._isTooManyWebsocketRetries(f)) return void (_.get(c, "errorNotification", !0) && h.error("Server connection interrupted.", {
-id:"websocket_retry_halted",
-mustDismiss:!0,
-actions:{
-refresh:{
-label:"Refresh",
-action:function() {
-window.location.reload();
-}
-}
-}
-}));
-if (this._addWebsocketEvent(f, "close"), e.shouldRelist) {
-i.log("Relisting for resource/context", a, b);
-var j = this;
-return void setTimeout(function() {
-j.watch(a, b);
-}, 2e3);
-}
-i.log("Rewatching for resource/context", a, b), this._watchInFlight(f, !0), setTimeout($.proxy(this, "_startWatchOp", f, a, b, c, this._resourceVersion(f)), 2e3);
-};
-var t = "{protocol}://{+hostPort}{+prefix}{/group}/{version}/", u = t + "{resource}{?q*}", v = t + "{resource}/{name}{/subresource*}{?q*}", w = t + "namespaces/{namespace}/{resource}{?q*}", x = t + "namespaces/{namespace}/{resource}/{name}{/subresource*}{?q*}";
-o.prototype._urlForResource = function(a, b, c, d, e) {
-var f = g.apiInfo(a);
-if (!f) return i.error("_urlForResource called with unknown resource", a, arguments), null;
-var h;
-e = e || {}, h = d ? "http:" === window.location.protocol ? "ws" :"wss" :"http:" === window.location.protocol ? "http" :"https", c && c.namespace && !e.namespace && (e.namespace = c.namespace);
-var j = e.namespace, k = null;
-j && (k = e.namespace, e = angular.copy(e), delete e.namespace);
-var l, m = {
-protocol:h,
-hostPort:f.hostPort,
-prefix:f.prefix,
-group:f.group,
-version:f.version,
-resource:a.primaryResource(),
-subresource:a.subresources(),
-name:b,
-namespace:k,
-q:e
-};
-return l = b ? j ? x :v :j ? w :u, URI.expand(l, m).toString();
-}, o.prototype.url = function(a) {
-if (a && a.resource) {
-var b = angular.copy(a);
-delete b.resource, delete b.group, delete b.version, delete b.name, delete b.isWebsocket;
-var c = g.toResourceGroupVersion({
-resource:a.resource,
-group:a.group,
-version:a.version
-});
-return this._urlForResource(c, a.name, null, !!a.isWebsocket, b);
-}
-return null;
-}, o.prototype.openshiftAPIBaseUrl = function() {
-var a = "http:" === window.location.protocol ? "http" :"https", b = f.openshift.hostPort;
-return new URI({
-protocol:a,
-hostname:b
-}).toString();
-};
-var y = {
-imagestreamimages:!0
-};
-return o.prototype._isImmutable = function(a) {
-return !!y[a.resource];
-}, o.prototype._hasImmutable = function(a, b, c) {
-return this._isImmutable(a) && b && b.by("metadata.name")[c];
-}, o.prototype._getNamespace = function(a, b, c) {
-var d = e.defer();
-return c.namespace ? d.resolve({
-namespace:c.namespace
-}) :b.projectPromise && !a.equals("projects") ? b.projectPromise.done(function(a) {
-d.resolve({
-namespace:a.metadata.name
-});
-}) :d.resolve(null), d.promise;
-}, new o();
-} ]), angular.module("openshiftConsole").factory("APIDiscovery", [ "LOGGING_URL", "METRICS_URL", "$q", function(a, b, c) {
+}), angular.module("openshiftConsole").factory("APIDiscovery", [ "LOGGING_URL", "METRICS_URL", "$q", function(a, b, c) {
return {
getLoggingURL:function() {
return c.when(a);
@@ -2039,127 +950,6 @@ var d = b(a, c);
localStorage.setItem(d, "true");
}
};
-}), angular.module("openshiftConsole").provider("RedirectLoginService", function() {
-var a = "", b = "", c = "";
-this.OAuthClientID = function(b) {
-return b && (a = b), a;
-}, this.OAuthAuthorizeURI = function(a) {
-return a && (b = a), b;
-}, this.OAuthRedirectURI = function(a) {
-return a && (c = a), c;
-}, this.$get = [ "$location", "$q", "Logger", "base64", function(d, e, f, g) {
-var h = f.get("auth"), i = function(a) {
-var b;
-if (window.crypto && window.Uint32Array) try {
-var c = new Uint32Array(a);
-window.crypto.getRandomValues(c), b = [];
-for (var d = 0; d < a; d++) b.push(c[d]);
-} catch (e) {
-h.debug("RedirectLoginService.getRandomInts: ", e), b = null;
-}
-if (!b) {
-b = [];
-for (var f = 0; f < a; f++) b.push(Math.floor(4294967296 * Math.random()));
-}
-return b;
-}, j = "RedirectLoginService.nonce", k = function(a) {
-var b = String(new Date().getTime()) + "-" + i(8).join("");
-try {
-window.localStorage[j] = b;
-} catch (c) {
-h.log("RedirectLoginService.makeState, localStorage error: ", c);
-}
-return g.urlencode(JSON.stringify({
-then:a,
-nonce:b
-}));
-}, l = function(a) {
-var b = {
-then:null,
-verified:!1
-}, c = "";
-try {
-c = window.localStorage[j], window.localStorage.removeItem(j);
-} catch (d) {
-h.log("RedirectLoginService.parseState, localStorage error: ", d);
-}
-try {
-var e = a ? JSON.parse(g.urldecode(a)) :{};
-e && e.nonce && c && e.nonce === c && (b.verified = !0, b.then = e.then);
-} catch (d) {
-h.error("RedirectLoginService.parseState, state error: ", d);
-}
-return h.error("RedirectLoginService.parseState", b), b;
-};
-return {
-login:function() {
-if ("" === a) return e.reject({
-error:"invalid_request",
-error_description:"RedirectLoginServiceProvider.OAuthClientID() not set"
-});
-if ("" === b) return e.reject({
-error:"invalid_request",
-error_description:"RedirectLoginServiceProvider.OAuthAuthorizeURI() not set"
-});
-if ("" === c) return e.reject({
-error:"invalid_request",
-error_description:"RedirectLoginServiceProvider.OAuthRedirectURI not set"
-});
-var f = e.defer(), g = new URI(b), i = new URI(d.url()).fragment("");
-return g.query({
-client_id:a,
-response_type:"token",
-state:k(i.toString()),
-redirect_uri:c
-}), h.log("RedirectLoginService.login(), redirecting", g.toString()), window.location.href = g.toString(), f.promise;
-},
-finish:function() {
-var a = new URI(d.url()), b = a.query(!0), c = new URI("?" + a.fragment()).query(!0);
-h.log("RedirectLoginService.finish()", b, c);
-var f = b.error || c.error;
-if (f) {
-var g = b.error_description || c.error_description, i = b.error_uri || c.error_uri;
-return h.log("RedirectLoginService.finish(), error", f, g, i), e.reject({
-error:f,
-error_description:g,
-error_uri:i
-});
-}
-var j = l(c.state);
-if (c.access_token && "bearer" === (c.token_type || "").toLowerCase()) {
-var k = e.defer();
-return k.resolve({
-token:c.access_token,
-ttl:c.expires_in,
-then:j.then,
-verified:j.verified
-}), k.promise;
-}
-return e.reject({
-error:"invalid_request",
-error_description:"No API token returned"
-});
-}
-};
-} ];
-}), angular.module("openshiftConsole").provider("DeleteTokenLogoutService", function() {
-this.$get = [ "$q", "$injector", "Logger", function(a, b, c) {
-var d = c.get("auth");
-return {
-logout:function(c, e) {
-if (d.log("DeleteTokenLogoutService.logout()", c, e), !e) return d.log("DeleteTokenLogoutService, no token, returning immediately"), a.when({});
-var f = b.get("DataService"), g = {
-http:{
-auth:{
-token:e,
-triggerLogin:!1
-}
-}
-};
-return f["delete"]("oauthaccesstokens", e, {}, g);
-}
-};
-} ];
}), angular.module("openshiftConsole").service("Navigate", [ "$location", "$window", "$timeout", "annotationFilter", "LabelFilter", "$filter", "APIService", function(a, b, c, d, e, f, g) {
var h = f("annotation"), i = f("buildConfigForBuild"), j = f("isJenkinsPipelineStrategy"), k = f("displayName"), l = function(a, b) {
return _.get(b, "isPipeline") ? "pipelines" :_.isObject(a) && j(a) ? "pipelines" :"builds";
@@ -2377,41 +1167,6 @@ b >= 0 && this.tasks.splice(b, 1);
}, b.prototype.clear = function() {
d.tasks = [];
}, d;
-} ]), angular.module("openshiftConsole").factory("Notification", [ "$rootScope", function(a) {
-function b() {
-this.messenger = Messenger({
-extraClasses:"messenger-fixed messenger-on-bottom messenger-on-right",
-theme:"flat",
-messageDefaults:{
-showCloseButton:!0,
-hideAfter:10
-}
-});
-var b = this;
-a.$on("$routeChangeStart", function(a, c, d) {
-b.clear();
-});
-}
-return b.prototype.notify = function(a, b, c) {
-c = c || {};
-var d = {
-type:a,
-message:$("").text(b).html(),
-id:c.id,
-actions:c.actions
-};
-c.mustDismiss && (d.hideAfter = !1), this.messenger.post(d);
-}, b.prototype.success = function(a, b) {
-this.notify("success", a, b);
-}, b.prototype.info = function(a, b) {
-this.notify("info", a, b);
-}, b.prototype.error = function(a, b) {
-this.notify("error", a, b);
-}, b.prototype.warning = function(a, b) {
-this.notify("warning", a, b);
-}, b.prototype.clear = function() {
-this.messenger.hideAll();
-}, new b();
} ]), angular.module("openshiftConsole").factory("ImageStreamResolver", [ "$q", "DataService", function(a, b) {
function c() {}
return c.prototype.fetchReferencedImageStreamImages = function(c, d, e, f) {
@@ -2442,6 +1197,16 @@ c.image && (b[c.dockerImageReference] = a.metadata.name + "@" + c.image);
});
});
}, new c();
+} ]), angular.module("openshiftConsole").factory("ContainerWebSocket", [ "API_CFG", "$ws", function(a, b) {
+return function(c, d) {
+var e;
+return 0 === c.indexOf("/") && (e = "http:" === window.location.protocol ? "ws://" :"wss://", c = e + a.openshift.hostPort + c), b({
+url:c,
+method:"WATCH",
+protocols:d,
+auth:{}
+});
+};
} ]), angular.module("openshiftConsole").factory("BaseHref", [ "$document", function(a) {
return a.find("base").attr("href") || "/";
} ]), angular.module("openshiftConsole").factory("BuildsService", [ "DataService", "$filter", function(a, b) {
@@ -3260,10 +2025,7 @@ var i = a.objectToResourceGroupVersion(f);
return b.update(i, f.metadata.name, f, e);
}
};
-} ]), angular.module("openshiftConsole").factory("Constants", function() {
-var a = _.clone(window.OPENSHIFT_CONSTANTS || {}), b = _.clone(window.OPENSHIFT_VERSION || {});
-return a.VERSION = b, a;
-}), angular.module("openshiftConsole").factory("LimitRangesService", [ "$filter", "LIMIT_REQUEST_OVERRIDES", function(a, b) {
+} ]), angular.module("openshiftConsole").factory("LimitRangesService", [ "$filter", "LIMIT_REQUEST_OVERRIDES", function(a, b) {
var c = a("usageValue"), d = a("usageWithUnits"), e = a("amountAndUnit"), f = function(a, b) {
return !!a && (!b || c(a) < c(b));
}, g = function(a, b) {
diff --git a/dist/scripts/vendor.js b/dist/scripts/vendor.js
index b818ea520b..e2976477e1 100644
--- a/dist/scripts/vendor.js
+++ b/dist/scripts/vendor.js
@@ -86,6 +86,10 @@ a || "" === a ? this.addConjunct(b, "in", [ a ]) :this.addConjunct(b, "exists",
}, this));
}
+function ResourceGroupVersion(a, b, c) {
+return this.resource = a, this.group = b, this.version = c, this;
+}
+
var g = void 0, j = !0, k = null, l = !1, n = window, o, p = Object.prototype, da = Function.prototype.apply, q = Array.prototype.slice, r = String.prototype.split, ea = Array.prototype.splice, s, fa, ga, t = Function.prototype.bind || function(a, b) {
var c = this, d = q.call(arguments, 1);
return function() {
@@ -59528,4 +59532,1287 @@ return b = b || a.name || "download", c || (a = n(a)), navigator.msSaveOrOpenBlo
"undefined" != typeof module && module.exports ? module.exports.saveAs = saveAs :"undefined" != typeof define && null !== define && null !== define.amd && define("FileSaver.js", function() {
return saveAs;
-});
\ No newline at end of file
+}), angular.module("openshiftCommon", [ "ab-base64" ]).config([ "AuthServiceProvider", function(a) {
+a.UserStore("MemoryUserStore");
+} ]).constant("API_CFG", _.get(window.OPENSHIFT_CONFIG, "api", {})).constant("APIS_CFG", _.get(window.OPENSHIFT_CONFIG, "apis", {})).constant("AUTH_CFG", _.get(window.OPENSHIFT_CONFIG, "auth", {})).config([ "$httpProvider", "AuthServiceProvider", "RedirectLoginServiceProvider", "AUTH_CFG", function(a, b, c, d) {
+a.interceptors.push("AuthInterceptor"), b.LoginService("RedirectLoginService"), b.LogoutService("DeleteTokenLogoutService"), b.UserStore("LocalStorageUserStore"), c.OAuthClientID(d.oauth_client_id), c.OAuthAuthorizeURI(d.oauth_authorize_uri), c.OAuthRedirectURI(URI(d.oauth_redirect_base).segment("oauth").toString());
+} ]), hawtioPluginLoader.addModule("openshiftCommon"), hawtioPluginLoader.registerPreBootstrapTask(function(a) {
+if (_.get(window, "OPENSHIFT_CONFIG.api.k8s.resources")) return void a();
+var b = {
+k8s:{},
+openshift:{}
+}, c = {}, d = [], e = window.location.protocol + "//", f = e + window.OPENSHIFT_CONFIG.api.k8s.hostPort + window.OPENSHIFT_CONFIG.api.k8s.prefix, g = $.get(f + "/v1").done(function(a) {
+b.k8s.v1 = _.indexBy(a.resources, "name");
+}).fail(function(a, b, c) {
+d.push({
+data:a,
+textStatus:b,
+xhr:c
+});
+}), h = e + window.OPENSHIFT_CONFIG.api.openshift.hostPort + window.OPENSHIFT_CONFIG.api.openshift.prefix, i = $.get(h + "/v1").done(function(a) {
+b.openshift.v1 = _.indexBy(a.resources, "name");
+}).fail(function(a, b, c) {
+d.push({
+data:a,
+textStatus:b,
+xhr:c
+});
+}), j = e + window.OPENSHIFT_CONFIG.apis.hostPort + window.OPENSHIFT_CONFIG.apis.prefix, k = $.get(j).then(function(a) {
+var b = [];
+return _.each(a.groups, function(a) {
+var e = {
+name:a.name,
+preferredVersion:a.preferredVersion.version,
+versions:{}
+};
+c[e.name] = e, _.each(a.versions, function(a) {
+var c = a.version;
+e.versions[c] = {
+version:c,
+groupVersion:a.groupVersion
+}, b.push($.get(j + "/" + a.groupVersion).done(function(a) {
+e.versions[c].resources = _.indexBy(a.resources, "name");
+}).fail(function(a, b, c) {
+d.push({
+data:a,
+textStatus:b,
+xhr:c
+});
+}));
+});
+}), $.when.apply(this, b);
+}, function(a, b, c) {
+d.push({
+data:a,
+textStatus:b,
+xhr:c
+});
+}), l = function() {
+window.OPENSHIFT_CONFIG.api.k8s.resources = b.k8s, window.OPENSHIFT_CONFIG.api.openshift.resources = b.openshift, window.OPENSHIFT_CONFIG.apis.groups = c, d.length && (window.OPENSHIFT_CONFIG.apis.API_DISCOVERY_ERRORS = d), a();
+};
+$.when(g, i, k).always(l);
+}), window.OPENSHIFT_CONFIG || (window.OPENSHIFT_CONFIG = {
+apis:{
+hostPort:"localhost:8443",
+prefix:"/apis"
+},
+api:{
+openshift:{
+hostPort:"localhost:8443",
+prefix:"/oapi"
+},
+k8s:{
+hostPort:"localhost:8443",
+prefix:"/api"
+}
+},
+auth:{
+oauth_authorize_uri:"https://localhost:8443/oauth/authorize",
+oauth_redirect_base:"https://localhost:9000/dev-console",
+oauth_client_id:"openshift-web-console",
+logout_uri:""
+},
+loggingURL:"",
+metricsURL:""
+}, window.OPENSHIFT_VERSION = {
+openshift:"dev-mode",
+kubernetes:"dev-mode"
+}), ResourceGroupVersion.prototype.toString = function() {
+var a = this.resource;
+return this.group && (a += "/" + this.group), this.version && (a += "/" + this.version), a;
+}, ResourceGroupVersion.prototype.primaryResource = function() {
+if (!this.resource) return "";
+var a = this.resource.indexOf("/");
+return a === -1 ? this.resource :this.resource.substring(0, a);
+}, ResourceGroupVersion.prototype.subresources = function() {
+var a = (this.resource || "").split("/");
+return a.shift(), a;
+}, ResourceGroupVersion.prototype.equals = function(a, b, c) {
+return this.resource === a && (1 === arguments.length || this.group === b && (2 === arguments.length || this.version === c));
+}, angular.module("openshiftCommon").factory("APIService", [ "API_CFG", "APIS_CFG", "AuthService", "Constants", "Logger", "$q", "$http", "$filter", "$window", function(a, b, c, d, e, f, g, h, i) {
+function j(a) {
+if (!a) return a;
+var b = a.indexOf("/");
+return b === -1 ? a.toLowerCase() :a.substring(0, b).toLowerCase() + a.substring(b);
+}
+function k(a, b) {
+if (!a) return "";
+var c = a;
+if (b) {
+var d = h("humanizeKind");
+c = d(c);
+}
+return c = String(c).toLowerCase(), "endpoints" === c || "securitycontextconstraints" === c || ("s" === c[c.length - 1] ? c += "es" :"y" === c[c.length - 1] ? c = c.substring(0, c.length - 1) + "ies" :c += "s"), c;
+}
+var l = {
+"":"v1",
+extensions:"v1beta1"
+}, m = function(a) {
+if (a instanceof ResourceGroupVersion) return a;
+var c, d, e;
+return angular.isString(a) ? (c = j(a), d = "", e = l[d]) :a && a.resource && (c = j(a.resource), d = a.group || "", e = a.version || l[d] || _.get(b, [ "groups", d, "preferredVersion" ])), new ResourceGroupVersion(c, d, e);
+}, n = function(a) {
+if (a) {
+var b = a.split("/");
+return 1 === b.length ? "v1" === b[0] ? {
+group:"",
+version:b[0]
+} :{
+group:b[0],
+version:""
+} :2 === b.length ? {
+group:b[0],
+version:b[1]
+} :void e.warn('Invalid apiVersion "' + a + '"');
+}
+}, o = function(a) {
+if (a && a.kind && a.apiVersion) {
+var b = k(a.kind);
+if (b) {
+var c = n(a.apiVersion);
+if (c) return new ResourceGroupVersion(b, c.group, c.version);
+}
+}
+}, p = function(a, b) {
+if (a && b) {
+var c = k(b.kind), d = n(b.apiVersion), e = m(a);
+if (c && d && e) return angular.isString(a) ? (e.equals(c) && (e.group = d.group, e.version = d.version), e) :(e.equals(c, d.group) && (e.version = d.version), e);
+}
+}, q = function(d) {
+if (b.API_DISCOVERY_ERRORS) {
+var e = _.every(b.API_DISCOVERY_ERRORS, function(a) {
+return 0 === _.get(a, "data.status");
+});
+return e && !c.isLoggedIn() ? void c.withUser() :void (i.location.href = URI("error").query({
+error_description:"Unable to load details about the server. If the problem continues, please contact your system administrator.",
+error:"API_DISCOVERY"
+}).toString());
+}
+d = m(d);
+var f = d.primaryResource();
+if (d.group) {
+if (!_.get(b, [ "groups", d.group, "versions", d.version, "resources", f ])) return;
+return {
+hostPort:b.hostPort,
+prefix:b.prefix,
+group:d.group,
+version:d.version
+};
+}
+var g;
+for (var h in a) if (g = a[h], _.get(g, [ "resources", d.version, f ])) return {
+hostPort:g.hostPort,
+prefix:g.prefix,
+version:d.version
+};
+}, r = function(a) {
+var b = "", c = "";
+return a && a.kind && (b = a.kind), a && a.apiVersion && (c = a.apiVersion), "Invalid kind (" + b + ") or API version (" + c + ")";
+}, s = function(a) {
+var b = "", c = "";
+return a && a.kind && (b = a.kind), a && a.apiVersion && (c = a.apiVersion), "The API version " + c + " for kind " + b + " is not supported by this server";
+}, t = function(c) {
+var e = [], f = d.AVAILABLE_KINDS_BLACKLIST;
+return _.each(a, function(a) {
+_.each(a.resources.v1, function(a) {
+if (a.namespaced || c) {
+if (a.name.indexOf("/") >= 0 || _.contains(f, a.kind)) return;
+e.push({
+kind:a.kind
+});
+}
+});
+}), _.each(b.groups, function(a) {
+var b = l[a.name] || a.preferredVersion;
+_.each(a.versions[b].resources, function(b) {
+b.name.indexOf("/") >= 0 || _.contains(f, b.kind) || "autoscaling" === a.name && "HorizontalPodAutoscaler" === b.kind || "batch" === a.name && "Job" === b.kind || (b.namespaced || c) && e.push({
+kind:b.kind,
+group:a.name
+});
+});
+}), _.uniq(e, !1, function(a) {
+return a.group + "/" + a.kind;
+});
+}, u = t(!1), v = t(!0), w = function(a) {
+return a ? v :u;
+};
+return {
+toResourceGroupVersion:m,
+parseGroupVersion:n,
+objectToResourceGroupVersion:o,
+deriveTargetResource:p,
+kindToResource:k,
+apiInfo:q,
+invalidObjectKindOrVersion:r,
+unsupportedObjectKindOrVersion:s,
+availableKinds:w
+};
+} ]), angular.module("openshiftCommon").provider("AuthService", function() {
+var a = "";
+this.UserStore = function(b) {
+return b && (a = b), a;
+};
+var b = "";
+this.LoginService = function(a) {
+return a && (b = a), b;
+};
+var c = "";
+this.LogoutService = function(a) {
+return a && (c = a), c;
+};
+var d = function(a, b, c) {
+if (b) return angular.isString(b) ? a.get(b) :a.invoke(b);
+throw c + " not set";
+};
+this.$get = [ "$q", "$injector", "$log", "$rootScope", "Logger", function(e, f, g, h, i) {
+var j = i.get("auth");
+j.log("AuthServiceProvider.$get", arguments);
+var k = $.Callbacks(), l = $.Callbacks(), m = $.Callbacks(), n = null, o = null, p = d(f, a, "AuthServiceProvider.UserStore()");
+p.available() || i.error("AuthServiceProvider.$get user store " + a + " not available");
+var q = d(f, b, "AuthServiceProvider.LoginService()"), r = d(f, c, "AuthServiceProvider.LogoutService()");
+return {
+UserStore:function() {
+return p;
+},
+isLoggedIn:function() {
+return !!p.getUser();
+},
+withUser:function() {
+var a = p.getUser();
+return a ? (h.user = a, j.log("AuthService.withUser()", a), e.when(a)) :(j.log("AuthService.withUser(), calling startLogin()"), this.startLogin());
+},
+setUser:function(a, b, c) {
+j.log("AuthService.setUser()", a, b, c);
+var d = p.getUser();
+p.setUser(a, c), p.setToken(b, c), h.user = a;
+var e = d && d.metadata && d.metadata.name, f = a && a.metadata && a.metadata.name;
+e !== f && (j.log("AuthService.setUser(), user changed", d, a), m.fire(a));
+},
+requestRequiresAuth:function(a) {
+var b = !!a.auth;
+return j.log("AuthService.requestRequiresAuth()", a.url.toString(), b), b;
+},
+addAuthToRequest:function(a) {
+var b = "";
+return a && a.auth && a.auth.token ? (b = a.auth.token, j.log("AuthService.addAuthToRequest(), using token from request config", b)) :(b = p.getToken(), j.log("AuthService.addAuthToRequest(), using token from user store", b)), b ? ("WATCH" === a.method ? (a.url = URI(a.url).addQuery({
+access_token:b
+}).toString(), j.log("AuthService.addAuthToRequest(), added token param", a.url)) :(a.headers.Authorization = "Bearer " + b, j.log("AuthService.addAuthToRequest(), added token header", a.headers.Authorization)), !0) :(j.log("AuthService.addAuthToRequest(), no token available"), !1);
+},
+startLogin:function() {
+if (n) return j.log("Login already in progress"), n;
+var a = this;
+return n = q.login().then(function(b) {
+a.setUser(b.user, b.token, b.ttl), k.fire(b.user);
+})["catch"](function(a) {
+i.error(a);
+})["finally"](function() {
+n = null;
+});
+},
+startLogout:function() {
+if (o) return j.log("Logout already in progress"), o;
+var a = this, b = p.getUser(), c = p.getToken(), d = this.isLoggedIn();
+return o = r.logout(b, c).then(function() {
+j.log("Logout service success");
+})["catch"](function(a) {
+j.error("Logout service error", a);
+})["finally"](function() {
+a.setUser(null, null);
+var b = a.isLoggedIn();
+d && !b && l.fire(), o = null;
+});
+},
+onLogin:function(a) {
+k.add(a);
+},
+onLogout:function(a) {
+l.add(a);
+},
+onUserChanged:function(a) {
+m.add(a);
+}
+};
+} ];
+}).factory("AuthInterceptor", [ "$q", "AuthService", function(a, b) {
+var c = [];
+return {
+request:function(d) {
+if (!b.requestRequiresAuth(d)) return d;
+if (b.addAuthToRequest(d)) return d;
+if (d.auth && d.auth.triggerLogin === !1) return d;
+var e = a.defer();
+return c.push([ e, d, "request" ]), b.startLogin(), e.promise;
+},
+responseError:function(d) {
+var e = d.config.auth || {};
+if (!b.requestRequiresAuth(d.config)) return a.reject(d);
+if (e.triggerLogin === !1) return a.reject(d);
+var f = d.status;
+switch (f) {
+case 401:
+var g = a.defer();
+return c.push([ g, d.config, "responseError" ]), b.startLogin(), g.promise;
+
+default:
+return a.reject(d);
+}
+}
+};
+} ]), angular.module("openshiftCommon").factory("AuthorizationService", [ "$q", "$cacheFactory", "Logger", "$interval", "APIService", "DataService", function(a, b, c, d, e, f) {
+var g = null, h = b("rulesCache", {
+number:10
+}), i = !1, j = [ "localresourceaccessreviews", "localsubjectaccessreviews", "resourceaccessreviews", "selfsubjectrulesreviews", "subjectaccessreviews" ], k = function(a) {
+var b = {};
+return _.each(a, function(a) {
+_.each(a.apiGroups, function(c) {
+b[c] || (b[c] = {}), _.each(a.resources, function(d) {
+b[c][d] = a.verbs;
+});
+});
+}), b;
+}, l = function(a) {
+return "projectrequests" !== a && !_.contains(a, "/") && !_.contains(j, a);
+}, m = function(a) {
+return _.some(a, function(a) {
+return _.some(a.resources, function(b) {
+return l(b) && !_.isEmpty(_.intersection(a.verbs, [ "*", "create", "update" ]));
+});
+});
+}, n = function(b, d) {
+var j = a.defer();
+g = b;
+var l = h.get(b), n = "selfsubjectrulesreviews";
+if (!l || l.forceRefresh || d) if (e.apiInfo(n)) {
+c.log("AuthorizationService, loading user rules for " + b + " project");
+var o = {
+kind:"SelfSubjectRulesReview",
+apiVersion:"v1"
+};
+f.create(n, null, o, {
+namespace:b
+}).then(function(a) {
+var c = k(a.status.rules), d = m(a.status.rules);
+h.put(b, {
+rules:c,
+canAddToProject:d,
+forceRefresh:!1,
+cacheTimestamp:_.now()
+}), j.resolve();
+}, function() {
+i = !0, j.resolve();
+});
+} else c.log("AuthorizationService, resource 'selfsubjectrulesreviews' is not part of APIserver. Switching into permissive mode."), i = !0, j.resolve(); else c.log("AuthorizationService, using cached rules for " + b + " project"), _.now() - l.cacheTimestamp >= 6e5 && (l.forceRefresh = !0), j.resolve();
+return j.promise;
+}, o = function(a) {
+return _.get(h.get(a || g), [ "rules" ]);
+}, p = function(a, b, c, d) {
+var e = a[c];
+if (!e) return !1;
+var f = e[d];
+return !!f && (_.contains(f, b) || _.contains(f, "*"));
+}, q = function(a, b, c) {
+if (i) return !0;
+var d = e.toResourceGroupVersion(a), f = o(c || g);
+return !!f && (p(f, b, d.group, d.resource) || p(f, b, "*", "*") || p(f, b, d.group, "*") || p(f, b, "*", d.resource));
+}, r = function(a) {
+return !!i || !!_.get(h.get(a || g), [ "canAddToProject" ]);
+};
+return {
+checkResource:l,
+getProjectRules:n,
+canI:q,
+canIAddToProject:r,
+getRulesForProject:o
+};
+} ]), angular.module("openshiftCommon").factory("base64util", function() {
+return {
+pad:function(a) {
+if (!a) return "";
+switch (a.length % 4) {
+case 1:
+return a + "===";
+
+case 2:
+return a + "==";
+
+case 3:
+return a + "=";
+
+default:
+return a;
+}
+}
+};
+}), angular.module("openshiftCommon").factory("Constants", function() {
+var a = _.clone(window.OPENSHIFT_CONSTANTS || {}), b = _.clone(window.OPENSHIFT_VERSION || {});
+return a.VERSION = b, a;
+}), angular.module("openshiftCommon").factory("DataService", [ "$cacheFactory", "$http", "$ws", "$rootScope", "$q", "API_CFG", "APIService", "Notification", "Logger", "$timeout", "base64", "base64util", function(a, b, c, d, e, f, g, h, i, j, k, l) {
+function m(a) {
+this._data = {}, this._objectsByAttribute(a, "metadata.name", this._data);
+}
+function n(a, b, c, d) {
+for (var e = b.split("."), f = a, g = 0; g < e.length; g++) if (f = f[e[g]], void 0 === f) return;
+if ($.isArray(f)) ; else if ($.isPlainObject(f)) for (var h in f) {
+var i = f[h];
+c[h] || (c[h] = {}), "DELETED" === d ? delete c[h][i] :c[h][i] = a;
+} else "DELETED" === d ? delete c[f] :c[f] = a;
+}
+function o() {
+this._listDeferredMap = {}, this._watchCallbacksMap = {}, this._watchObjectCallbacksMap = {}, this._watchOperationMap = {}, this._listOperationMap = {}, this._resourceVersionMap = {}, this._dataCache = a("dataCache", {
+number:25
+}), this._immutableDataCache = a("immutableDataCache", {
+number:50
+}), this._watchOptionsMap = {}, this._watchWebsocketsMap = {}, this._watchPollTimeoutsMap = {}, this._websocketEventsMap = {};
+var b = this;
+d.$on("$routeChangeStart", function(a, c, d) {
+b._websocketEventsMap = {};
+});
+}
+function p(a) {
+var b = 3e4;
+return a.length >= r && Date.now() - a[0].time < b;
+}
+function q(a) {
+var b = 5;
+if (a.length < b) return !1;
+for (var c = a.length - b; c < a.length; c++) if ("close" !== a[c].type) return !1;
+return !0;
+}
+m.prototype.by = function(a) {
+if ("metadata.name" === a) return this._data;
+var b = {};
+for (var c in this._data) n(this._data[c], a, b, null);
+return b;
+}, m.prototype.update = function(a, b) {
+n(a, "metadata.name", this._data, b);
+}, m.prototype._objectsByAttribute = function(a, b, c, d) {
+angular.forEach(a, function(a, e) {
+n(a, b, c, d ? d[e] :null);
+});
+}, o.prototype.list = function(a, b, c, d) {
+a = g.toResourceGroupVersion(a);
+var e = this._uniqueKey(a, null, b, _.get(d, "http.params")), f = this._listDeferred(e);
+return c && f.promise.then(c), this._isCached(e) ? f.resolve(this._data(e)) :this._listInFlight(e) || this._startListOp(a, b, d), f.promise;
+}, o.prototype["delete"] = function(a, c, d, f) {
+a = g.toResourceGroupVersion(a), f = f || {};
+var h, i = e.defer(), j = this, k = {};
+return _.has(f, "gracePeriodSeconds") && (h = {
+kind:"DeleteOptions",
+apiVersion:"v1",
+gracePeriodSeconds:f.gracePeriodSeconds
+}, k["Content-Type"] = "application/json"), this._getNamespace(a, d, f).then(function(e) {
+b(angular.extend({
+method:"DELETE",
+auth:{},
+data:h,
+headers:k,
+url:j._urlForResource(a, c, d, !1, e)
+}, f.http || {})).success(function(a, b, c, d, e) {
+i.resolve(a);
+}).error(function(a, b, c, d) {
+i.reject({
+data:a,
+status:b,
+headers:c,
+config:d
+});
+});
+}), i.promise;
+}, o.prototype.update = function(a, c, d, f, h) {
+a = g.deriveTargetResource(a, d), h = h || {};
+var i = e.defer(), j = this;
+return this._getNamespace(a, f, h).then(function(e) {
+b(angular.extend({
+method:"PUT",
+auth:{},
+data:d,
+url:j._urlForResource(a, c, f, !1, e)
+}, h.http || {})).success(function(a, b, c, d, e) {
+i.resolve(a);
+}).error(function(a, b, c, d) {
+i.reject({
+data:a,
+status:b,
+headers:c,
+config:d
+});
+});
+}), i.promise;
+}, o.prototype.create = function(a, c, d, f, h) {
+a = g.deriveTargetResource(a, d), h = h || {};
+var i = e.defer(), j = this;
+return this._getNamespace(a, f, h).then(function(e) {
+b(angular.extend({
+method:"POST",
+auth:{},
+data:d,
+url:j._urlForResource(a, c, f, !1, e)
+}, h.http || {})).success(function(a, b, c, d, e) {
+i.resolve(a);
+}).error(function(a, b, c, d) {
+i.reject({
+data:a,
+status:b,
+headers:c,
+config:d
+});
+});
+}), i.promise;
+}, o.prototype.batch = function(a, b, c, d) {
+function f() {
+0 === l && h.resolve({
+success:i,
+failure:j
+});
+}
+var h = e.defer(), i = [], j = [], k = this, l = a.length;
+return c = c || "create", _.each(a, function(a) {
+var e = g.objectToResourceGroupVersion(a);
+if (!e) return j.push({
+object:a,
+data:{
+message:g.invalidObjectKindOrVersion(a)
+}
+}), l--, void f();
+if (!g.apiInfo(e)) return j.push({
+object:a,
+data:{
+message:g.unsupportedObjectKindOrVersion(a)
+}
+}), l--, void f();
+var m = function(b) {
+b.object = a, i.push(b), l--, f();
+}, n = function(b) {
+b.object = a, j.push(b), l--, f();
+};
+switch (c) {
+case "create":
+k.create(e, null, a, b, d).then(m, n);
+break;
+
+case "update":
+k.update(e, a.metadata.name, a, b, d).then(m, n);
+break;
+
+default:
+return h.reject({
+data:"Invalid '" + c + "' action.",
+status:400,
+headers:function() {
+return null;
+},
+config:{},
+object:a
+});
+}
+}), h.promise;
+}, o.prototype.get = function(a, c, d, f) {
+a = g.toResourceGroupVersion(a), f = f || {};
+var i = this._uniqueKey(a, c, d, _.get(f, "http.params"));
+!!f.force;
+delete f.force;
+var k = e.defer(), l = this._immutableData(i);
+if (this._hasImmutable(a, l, c)) j(function() {
+k.resolve(l.by("metadata.name")[c]);
+}, 0); else {
+var m = this;
+this._getNamespace(a, d, f).then(function(e) {
+b(angular.extend({
+method:"GET",
+auth:{},
+url:m._urlForResource(a, c, d, !1, e)
+}, f.http || {})).success(function(b, c, d, e, f) {
+m._isImmutable(a) && (l ? l.update(b, "ADDED") :m._immutableData(i, [ b ])), k.resolve(b);
+}).error(function(b, d, e, g) {
+if (f.errorNotification !== !1) {
+var i = "Failed to get " + a + "/" + c;
+0 !== d && (i += " (" + d + ")"), h.error(i);
+}
+k.reject({
+data:b,
+status:d,
+headers:e,
+config:g
+});
+});
+});
+}
+return k.promise;
+}, o.prototype.createStream = function(a, b, d, e, f) {
+var h = this;
+a = g.toResourceGroupVersion(a);
+var j, m = f ? "binary.k8s.io" :"base64.binary.k8s.io", n = "stream_", o = {}, p = {}, q = {}, r = {}, s = function() {
+return h._getNamespace(a, d, {}).then(function(g) {
+var j = 0;
+return c({
+url:h._urlForResource(a, b, d, !0, _.extend(g, e)),
+auth:{},
+onopen:function(a) {
+_.each(o, function(b) {
+b(a);
+});
+},
+onmessage:function(a) {
+if (!_.isString(a.data)) return void i.log("log stream response is not a string", a.data);
+var b;
+f || (b = k.decode(l.pad(a.data)), j += b.length), _.each(p, function(c) {
+f ? c(a.data) :c(b, a.data, j);
+});
+},
+onclose:function(a) {
+_.each(q, function(b) {
+b(a);
+});
+},
+onerror:function(a) {
+_.each(r, function(b) {
+b(a);
+});
+},
+protocols:m
+}).then(function(a) {
+return i.log("Streaming pod log", a), a;
+});
+});
+};
+return {
+onOpen:function(a) {
+if (_.isFunction(a)) {
+var b = _.uniqueId(n);
+return o[b] = a, b;
+}
+},
+onMessage:function(a) {
+if (_.isFunction(a)) {
+var b = _.uniqueId(n);
+return p[b] = a, b;
+}
+},
+onClose:function(a) {
+if (_.isFunction(a)) {
+var b = _.uniqueId(n);
+return q[b] = a, b;
+}
+},
+onError:function(a) {
+if (_.isFunction(a)) {
+var b = _.uniqueId(n);
+return r[b] = a, b;
+}
+},
+remove:function(a) {
+o[a] && delete o[a], p[a] && delete p[a], q[a] && delete q[a], r[a] && delete r[a];
+},
+start:function() {
+return j = s();
+},
+stop:function() {
+j.then(function(a) {
+a.close();
+});
+}
+};
+}, o.prototype.watch = function(a, b, c, d) {
+a = g.toResourceGroupVersion(a), d = d || {};
+var e = this._uniqueKey(a, null, b, _.get(d, "http.params"));
+if (c) this._watchCallbacks(e).add(c); else if (!this._watchCallbacks(e).has()) return {};
+var f = this._watchOptions(e);
+if (f) {
+if (!!f.poll != !!d.poll) throw "A watch already exists for " + a + " with a different polling option.";
+} else this._watchOptions(e, d);
+var h = this;
+if (this._isCached(e)) c && j(function() {
+c(h._data(e));
+}, 0); else {
+if (c) {
+var i = this._resourceVersion(e);
+this._data(e) && j(function() {
+i === h._resourceVersion(e) && c(h._data(e));
+}, 0);
+}
+this._listInFlight(e) || this._startListOp(a, b, d);
+}
+return {
+resource:a,
+context:b,
+callback:c,
+opts:d
+};
+}, o.prototype.watchObject = function(a, b, c, d, e) {
+a = g.toResourceGroupVersion(a), e = e || {};
+var f, h = this._uniqueKey(a, b, c, _.get(e, "http.params"));
+if (d) {
+this._watchObjectCallbacks(h).add(d);
+var i = this;
+f = function(a, c, d) {
+if (d && d.metadata.name === b) i._watchObjectCallbacks(h).fire(d, c); else if (!d) {
+var e = a.by("metadata.name");
+e[b] && i._watchObjectCallbacks(h).fire(e[b]);
+}
+};
+} else if (!this._watchObjectCallbacks(h).has()) return {};
+var j = this.watch(a, c, f, e);
+return j.objectCallback = d, j.objectName = b, j;
+}, o.prototype.unwatch = function(a) {
+var b = a.resource, c = a.objectName, d = a.context, e = a.callback, f = a.objectCallback, g = a.opts, h = this._uniqueKey(b, null, d, _.get(g, "http.params"));
+if (f && c) {
+var i = this._uniqueKey(b, c, d, _.get(g, "http.params")), j = this._watchObjectCallbacks(i);
+j.remove(f);
+}
+var k = this._watchCallbacks(h);
+if (e && k.remove(e), !k.has()) {
+if (g && g.poll) clearTimeout(this._watchPollTimeouts(h)), this._watchPollTimeouts(h, null); else if (this._watchWebsockets(h)) {
+var l = this._watchWebsockets(h);
+l.shouldClose = !0, l.close(), this._watchWebsockets(h, null);
+}
+this._watchInFlight(h, !1), this._watchOptions(h, null);
+}
+}, o.prototype.unwatchAll = function(a) {
+for (var b = 0; b < a.length; b++) this.unwatch(a[b]);
+}, o.prototype._watchCallbacks = function(a) {
+return this._watchCallbacksMap[a] || (this._watchCallbacksMap[a] = $.Callbacks()), this._watchCallbacksMap[a];
+}, o.prototype._watchObjectCallbacks = function(a) {
+return this._watchObjectCallbacksMap[a] || (this._watchObjectCallbacksMap[a] = $.Callbacks()), this._watchObjectCallbacksMap[a];
+}, o.prototype._listDeferred = function(a) {
+return this._listDeferredMap[a] || (this._listDeferredMap[a] = e.defer()), this._listDeferredMap[a];
+}, o.prototype._watchInFlight = function(a, b) {
+return b || b === !1 ? void (this._watchOperationMap[a] = b) :this._watchOperationMap[a];
+}, o.prototype._listInFlight = function(a, b) {
+return b || b === !1 ? void (this._listOperationMap[a] = b) :this._listOperationMap[a];
+}, o.prototype._resourceVersion = function(a, b) {
+return b ? void (this._resourceVersionMap[a] = b) :this._resourceVersionMap[a];
+}, o.prototype._data = function(a, b) {
+return b ? this._dataCache.put(a, new m(b)) :this._dataCache.get(a);
+}, o.prototype._immutableData = function(a, b) {
+return b ? this._immutableDataCache.put(a, new m(b)) :this._immutableDataCache.get(a);
+}, o.prototype._isCached = function(a) {
+return this._watchInFlight(a) && this._resourceVersion(a) && !!this._data(a);
+}, o.prototype._watchOptions = function(a, b) {
+return void 0 === b ? this._watchOptionsMap[a] :void (this._watchOptionsMap[a] = b);
+}, o.prototype._watchPollTimeouts = function(a, b) {
+return b ? void (this._watchPollTimeoutsMap[a] = b) :this._watchPollTimeoutsMap[a];
+}, o.prototype._watchWebsockets = function(a, b) {
+return b ? void (this._watchWebsocketsMap[a] = b) :this._watchWebsocketsMap[a];
+};
+var r = 10;
+o.prototype._addWebsocketEvent = function(a, b) {
+var c = this._websocketEventsMap[a];
+for (c || (c = this._websocketEventsMap[a] = []), c.push({
+type:b,
+time:Date.now()
+}); c.length > r; ) c.shift();
+}, o.prototype._isTooManyWebsocketRetries = function(a) {
+var b = this._websocketEventsMap[a];
+return !!b && (p(b) ? (i.log("Too many websocket open or close events for resource/context in a short period", a, b), !0) :!!q(b) && (i.log("Too many consecutive websocket close events for resource/context", a, b), !0));
+};
+var s = function(a) {
+var b = _.keysIn(_.pick(a, [ "fieldSelector", "labelSelector" ])).sort();
+return _.reduce(b, function(c, d, e) {
+return c + d + "=" + encodeURIComponent(a[d]) + (e < b.length - 1 ? "&" :"");
+}, "?");
+};
+o.prototype._uniqueKey = function(a, b, c, d) {
+var e = c && c.namespace || _.get(c, "project.metadata.name") || c.projectName;
+return this._urlForResource(a, b, c, null, angular.extend({}, {}, {
+namespace:e
+})).toString() + s(d || {});
+}, o.prototype._startListOp = function(a, c, d) {
+d = d || {};
+var e = this._uniqueKey(a, null, c, _.get(d, "http.params"));
+this._listInFlight(e, !0);
+var f = this;
+c.projectPromise && !a.equals("projects") ? c.projectPromise.done(function(g) {
+b(angular.extend({
+method:"GET",
+auth:{},
+url:f._urlForResource(a, null, c, !1, {
+namespace:g.metadata.name
+})
+}, d.http || {})).success(function(b, g, h, i, j) {
+f._listOpComplete(e, a, c, d, b);
+}).error(function(b, c, g, i) {
+f._listInFlight(e, !1);
+var j = f._listDeferred(e);
+if (delete f._listDeferredMap[e], j.reject(b, c, g, i), _.get(d, "errorNotification", !0)) {
+var k = "Failed to list " + a;
+0 !== c && (k += " (" + c + ")"), h.error(k);
+}
+});
+}) :b({
+method:"GET",
+auth:{},
+url:this._urlForResource(a, null, c)
+}).success(function(b, g, h, i, j) {
+f._listOpComplete(e, a, c, d, b);
+}).error(function(b, c, g, i) {
+f._listInFlight(e, !1);
+var j = f._listDeferred(e);
+if (delete f._listDeferredMap[e], j.reject(b, c, g, i), _.get(d, "errorNotification", !0)) {
+var k = "Failed to list " + a;
+0 !== c && (k += " (" + c + ")"), h.error(k);
+}
+});
+}, o.prototype._listOpComplete = function(a, b, c, d, e) {
+e.items || console.warn("List request for " + b + " returned a null items array. This is an invalid API response.");
+var f = e.items || [];
+e.kind && e.kind.indexOf("List") === e.kind.length - 4 && angular.forEach(f, function(a) {
+a.kind || (a.kind = e.kind.slice(0, -4)), a.apiVersion || (a.apiVersion = e.apiVersion);
+}), this._listInFlight(a, !1);
+var g = this._listDeferred(a);
+if (delete this._listDeferredMap[a], this._resourceVersion(a, e.resourceVersion || e.metadata.resourceVersion), this._data(a, f), g.resolve(this._data(a)), this._watchCallbacks(a).fire(this._data(a)), this._watchCallbacks(a).has()) {
+var h = this._watchOptions(a) || {};
+h.poll ? (this._watchInFlight(a, !0), this._watchPollTimeouts(a, setTimeout($.proxy(this, "_startListOp", b, c), h.pollInterval || 5e3))) :this._watchInFlight(a) || this._startWatchOp(a, b, c, d, this._resourceVersion(a));
+}
+}, o.prototype._startWatchOp = function(a, b, d, e, f) {
+if (this._watchInFlight(a, !0), c.available()) {
+var g = this, h = _.get(e, "http.params") || {};
+h.watch = !0, f && (h.resourceVersion = f), d.projectPromise && !b.equals("projects") ? d.projectPromise.done(function(f) {
+h.namespace = f.metadata.name, c({
+method:"WATCH",
+url:g._urlForResource(b, null, d, !0, h),
+auth:{},
+onclose:$.proxy(g, "_watchOpOnClose", b, d, e),
+onmessage:$.proxy(g, "_watchOpOnMessage", b, d, e),
+onopen:$.proxy(g, "_watchOpOnOpen", b, d, e)
+}).then(function(b) {
+i.log("Watching", b), g._watchWebsockets(a, b);
+});
+}) :c({
+method:"WATCH",
+url:g._urlForResource(b, null, d, !0, h),
+auth:{},
+onclose:$.proxy(g, "_watchOpOnClose", b, d, e),
+onmessage:$.proxy(g, "_watchOpOnMessage", b, d, e),
+onopen:$.proxy(g, "_watchOpOnOpen", b, d, e)
+}).then(function(b) {
+i.log("Watching", b), g._watchWebsockets(a, b);
+});
+}
+}, o.prototype._watchOpOnOpen = function(a, b, c, d) {
+i.log("Websocket opened for resource/context", a, b);
+var e = this._uniqueKey(a, null, b, _.get(c, "http.params"));
+this._addWebsocketEvent(e, "open");
+}, o.prototype._watchOpOnMessage = function(a, b, c, d) {
+var e = this._uniqueKey(a, null, b, _.get(c, "http.params"));
+try {
+var f = $.parseJSON(d.data);
+if ("ERROR" == f.type) return i.log("Watch window expired for resource/context", a, b), void (d.target && (d.target.shouldRelist = !0));
+"DELETED" === f.type && f.object && f.object.metadata && !f.object.metadata.deletionTimestamp && (f.object.metadata.deletionTimestamp = new Date().toISOString()), f.object && this._resourceVersion(e, f.object.resourceVersion || f.object.metadata.resourceVersion), this._data(e).update(f.object, f.type);
+var g = this;
+j(function() {
+g._watchCallbacks(e).fire(g._data(e), f.type, f.object);
+}, 0);
+} catch (h) {
+i.error("Error processing message", a, d.data);
+}
+}, o.prototype._watchOpOnClose = function(a, b, c, d) {
+var e = d.target, f = this._uniqueKey(a, null, b, _.get(c, "http.params"));
+if (!e) return void i.log("Skipping reopen, no eventWS in event", d);
+var g = this._watchWebsockets(f);
+if (!g) return void i.log("Skipping reopen, no registeredWS for resource/context", a, b);
+if (e !== g) return void i.log("Skipping reopen, eventWS does not match registeredWS", e, g);
+if (this._watchInFlight(f, !1), e.shouldClose) return void i.log("Skipping reopen, eventWS was explicitly closed", e);
+if (d.wasClean) return void i.log("Skipping reopen, clean close", d);
+if (!this._watchCallbacks(f).has()) return void i.log("Skipping reopen, no listeners registered for resource/context", a, b);
+if (this._isTooManyWebsocketRetries(f)) return void (_.get(c, "errorNotification", !0) && h.error("Server connection interrupted.", {
+id:"websocket_retry_halted",
+mustDismiss:!0,
+actions:{
+refresh:{
+label:"Refresh",
+action:function() {
+window.location.reload();
+}
+}
+}
+}));
+if (this._addWebsocketEvent(f, "close"), e.shouldRelist) {
+i.log("Relisting for resource/context", a, b);
+var j = this;
+return void setTimeout(function() {
+j.watch(a, b);
+}, 2e3);
+}
+i.log("Rewatching for resource/context", a, b), this._watchInFlight(f, !0), setTimeout($.proxy(this, "_startWatchOp", f, a, b, c, this._resourceVersion(f)), 2e3);
+};
+var t = "{protocol}://{+hostPort}{+prefix}{/group}/{version}/", u = t + "{resource}{?q*}", v = t + "{resource}/{name}{/subresource*}{?q*}", w = t + "namespaces/{namespace}/{resource}{?q*}", x = t + "namespaces/{namespace}/{resource}/{name}{/subresource*}{?q*}";
+o.prototype._urlForResource = function(a, b, c, d, e) {
+var f = g.apiInfo(a);
+if (!f) return i.error("_urlForResource called with unknown resource", a, arguments), null;
+var h;
+e = e || {}, h = d ? "http:" === window.location.protocol ? "ws" :"wss" :"http:" === window.location.protocol ? "http" :"https", c && c.namespace && !e.namespace && (e.namespace = c.namespace);
+var j = e.namespace, k = null;
+j && (k = e.namespace, e = angular.copy(e), delete e.namespace);
+var l, m = {
+protocol:h,
+hostPort:f.hostPort,
+prefix:f.prefix,
+group:f.group,
+version:f.version,
+resource:a.primaryResource(),
+subresource:a.subresources(),
+name:b,
+namespace:k,
+q:e
+};
+return l = b ? j ? x :v :j ? w :u, URI.expand(l, m).toString();
+}, o.prototype.url = function(a) {
+if (a && a.resource) {
+var b = angular.copy(a);
+delete b.resource, delete b.group, delete b.version, delete b.name, delete b.isWebsocket;
+var c = g.toResourceGroupVersion({
+resource:a.resource,
+group:a.group,
+version:a.version
+});
+return this._urlForResource(c, a.name, null, !!a.isWebsocket, b);
+}
+return null;
+}, o.prototype.openshiftAPIBaseUrl = function() {
+var a = "http:" === window.location.protocol ? "http" :"https", b = f.openshift.hostPort;
+return new URI({
+protocol:a,
+hostname:b
+}).toString();
+};
+var y = {
+imagestreamimages:!0
+};
+return o.prototype._isImmutable = function(a) {
+return !!y[a.resource];
+}, o.prototype._hasImmutable = function(a, b, c) {
+return this._isImmutable(a) && b && b.by("metadata.name")[c];
+}, o.prototype._getNamespace = function(a, b, c) {
+var d = e.defer();
+return c.namespace ? d.resolve({
+namespace:c.namespace
+}) :b.projectPromise && !a.equals("projects") ? b.projectPromise.done(function(a) {
+d.resolve({
+namespace:a.metadata.name
+});
+}) :d.resolve(null), d.promise;
+}, new o();
+} ]), angular.module("openshiftCommon").provider("DeleteTokenLogoutService", function() {
+this.$get = [ "$q", "$injector", "Logger", function(a, b, c) {
+var d = c.get("auth");
+return {
+logout:function(c, e) {
+if (d.log("DeleteTokenLogoutService.logout()", c, e), !e) return d.log("DeleteTokenLogoutService, no token, returning immediately"), a.when({});
+var f = b.get("DataService"), g = {
+http:{
+auth:{
+token:e,
+triggerLogin:!1
+}
+}
+};
+return f["delete"]("oauthaccesstokens", e, {}, g);
+}
+};
+} ];
+}), angular.module("openshiftCommon").provider("Logger", function() {
+this.$get = function() {
+var a = Logger.get("OpenShift"), b = {
+get:function(a) {
+var b = Logger.get("OpenShift/" + a), c = "OFF";
+return localStorage && (c = localStorage["OpenShiftLogLevel." + a] || c), b.setLevel(Logger[c]), b;
+},
+log:function() {
+a.log.apply(a, arguments);
+},
+info:function() {
+a.info.apply(a, arguments);
+},
+debug:function() {
+a.debug.apply(a, arguments);
+},
+warn:function() {
+a.warn.apply(a, arguments);
+},
+error:function() {
+a.error.apply(a, arguments);
+}
+}, c = "ERROR";
+return localStorage && (c = localStorage["OpenShiftLogLevel.main"] || c), a.setLevel(Logger[c]), b;
+};
+}), angular.module("openshiftCommon").provider("MemoryUserStore", function() {
+this.$get = [ "Logger", function(a) {
+var b = a.get("auth"), c = null, d = null;
+return {
+available:function() {
+return !0;
+},
+getUser:function() {
+return b.log("MemoryUserStore.getUser", c), c;
+},
+setUser:function(a, d) {
+b.log("MemoryUserStore.setUser", a), c = a;
+},
+getToken:function() {
+return b.log("MemoryUserStore.getToken", d), d;
+},
+setToken:function(a, c) {
+b.log("MemoryUserStore.setToken", a), d = a;
+}
+};
+} ];
+}).provider("SessionStorageUserStore", function() {
+this.$get = [ "Logger", function(a) {
+var b = a.get("auth"), c = "SessionStorageUserStore.user", d = "SessionStorageUserStore.token";
+return {
+available:function() {
+try {
+var a = String(new Date().getTime());
+sessionStorage["SessionStorageUserStore.test"] = a;
+var b = sessionStorage["SessionStorageUserStore.test"];
+return sessionStorage.removeItem("SessionStorageUserStore.test"), a === b;
+} catch (c) {
+return !1;
+}
+},
+getUser:function() {
+try {
+var a = JSON.parse(sessionStorage[c]);
+return b.log("SessionStorageUserStore.getUser", a), a;
+} catch (d) {
+return b.error("SessionStorageUserStore.getUser", d), null;
+}
+},
+setUser:function(a, d) {
+a ? (b.log("SessionStorageUserStore.setUser", a), sessionStorage[c] = JSON.stringify(a)) :(b.log("SessionStorageUserStore.setUser", a, "deleting"), sessionStorage.removeItem(c));
+},
+getToken:function() {
+try {
+var a = sessionStorage[d];
+return b.log("SessionStorageUserStore.getToken", a), a;
+} catch (c) {
+return b.error("SessionStorageUserStore.getToken", c), null;
+}
+},
+setToken:function(a, c) {
+a ? (b.log("SessionStorageUserStore.setToken", a), sessionStorage[d] = a) :(b.log("SessionStorageUserStore.setToken", a, "deleting"), sessionStorage.removeItem(d));
+}
+};
+} ];
+}).provider("LocalStorageUserStore", function() {
+this.$get = [ "Logger", function(a) {
+var b = a.get("auth"), c = "LocalStorageUserStore.user", d = "LocalStorageUserStore.token", e = function(a) {
+return a + ".ttl";
+}, f = function(a, c) {
+if (c) {
+var d = new Date().getTime() + 1e3 * c;
+localStorage[e(a)] = d, b.log("LocalStorageUserStore.setTTL", a, c, new Date(d).toString());
+} else localStorage.removeItem(e(a)), b.log("LocalStorageUserStore.setTTL deleting", a);
+}, g = function(a) {
+var c = localStorage[e(a)];
+if (!c) return !1;
+var d = parseInt(c) < new Date().getTime();
+return b.log("LocalStorageUserStore.isTTLExpired", a, d), d;
+};
+return {
+available:function() {
+try {
+var a = String(new Date().getTime());
+localStorage["LocalStorageUserStore.test"] = a;
+var b = localStorage["LocalStorageUserStore.test"];
+return localStorage.removeItem("LocalStorageUserStore.test"), a === b;
+} catch (c) {
+return !1;
+}
+},
+getUser:function() {
+try {
+if (g(c)) return b.log("LocalStorageUserStore.getUser expired"), localStorage.removeItem(c), f(c, null), null;
+var a = JSON.parse(localStorage[c]);
+return b.log("LocalStorageUserStore.getUser", a), a;
+} catch (d) {
+return b.error("LocalStorageUserStore.getUser", d), null;
+}
+},
+setUser:function(a, d) {
+a ? (b.log("LocalStorageUserStore.setUser", a, d), localStorage[c] = JSON.stringify(a), f(c, d)) :(b.log("LocalStorageUserStore.setUser", a, "deleting"), localStorage.removeItem(c), f(c, null));
+},
+getToken:function() {
+try {
+if (g(d)) return b.log("LocalStorageUserStore.getToken expired"), localStorage.removeItem(d), f(d, null), null;
+var a = localStorage[d];
+return b.log("LocalStorageUserStore.getToken", a), a;
+} catch (c) {
+return b.error("LocalStorageUserStore.getToken", c), null;
+}
+},
+setToken:function(a, c) {
+a ? (b.log("LocalStorageUserStore.setToken", a, c), localStorage[d] = a, f(d, c)) :(b.log("LocalStorageUserStore.setToken", a, c, "deleting"), localStorage.removeItem(d), f(d, null));
+}
+};
+} ];
+}), angular.module("openshiftCommon").factory("Notification", [ "$rootScope", function(a) {
+function b() {
+this.messenger = Messenger({
+extraClasses:"messenger-fixed messenger-on-bottom messenger-on-right",
+theme:"flat",
+messageDefaults:{
+showCloseButton:!0,
+hideAfter:10
+}
+});
+var b = this;
+a.$on("$routeChangeStart", function(a, c, d) {
+b.clear();
+});
+}
+return b.prototype.notify = function(a, b, c) {
+c = c || {};
+var d = {
+type:a,
+message:$("").text(b).html(),
+id:c.id,
+actions:c.actions
+};
+c.mustDismiss && (d.hideAfter = !1), this.messenger.post(d);
+}, b.prototype.success = function(a, b) {
+this.notify("success", a, b);
+}, b.prototype.info = function(a, b) {
+this.notify("info", a, b);
+}, b.prototype.error = function(a, b) {
+this.notify("error", a, b);
+}, b.prototype.warning = function(a, b) {
+this.notify("warning", a, b);
+}, b.prototype.clear = function() {
+this.messenger.hideAll();
+}, new b();
+} ]), angular.module("openshiftCommon").provider("RedirectLoginService", function() {
+var a = "", b = "", c = "";
+this.OAuthClientID = function(b) {
+return b && (a = b), a;
+}, this.OAuthAuthorizeURI = function(a) {
+return a && (b = a), b;
+}, this.OAuthRedirectURI = function(a) {
+return a && (c = a), c;
+}, this.$get = [ "$location", "$q", "Logger", "base64", function(d, e, f, g) {
+var h = f.get("auth"), i = function(a) {
+var b;
+if (window.crypto && window.Uint32Array) try {
+var c = new Uint32Array(a);
+window.crypto.getRandomValues(c), b = [];
+for (var d = 0; d < a; d++) b.push(c[d]);
+} catch (e) {
+h.debug("RedirectLoginService.getRandomInts: ", e), b = null;
+}
+if (!b) {
+b = [];
+for (var f = 0; f < a; f++) b.push(Math.floor(4294967296 * Math.random()));
+}
+return b;
+}, j = "RedirectLoginService.nonce", k = function(a) {
+var b = String(new Date().getTime()) + "-" + i(8).join("");
+try {
+window.localStorage[j] = b;
+} catch (c) {
+h.log("RedirectLoginService.makeState, localStorage error: ", c);
+}
+return g.urlencode(JSON.stringify({
+then:a,
+nonce:b
+}));
+}, l = function(a) {
+var b = {
+then:null,
+verified:!1
+}, c = "";
+try {
+c = window.localStorage[j], window.localStorage.removeItem(j);
+} catch (d) {
+h.log("RedirectLoginService.parseState, localStorage error: ", d);
+}
+try {
+var e = a ? JSON.parse(g.urldecode(a)) :{};
+e && e.nonce && c && e.nonce === c && (b.verified = !0, b.then = e.then);
+} catch (d) {
+h.error("RedirectLoginService.parseState, state error: ", d);
+}
+return h.error("RedirectLoginService.parseState", b), b;
+};
+return {
+login:function() {
+if ("" === a) return e.reject({
+error:"invalid_request",
+error_description:"RedirectLoginServiceProvider.OAuthClientID() not set"
+});
+if ("" === b) return e.reject({
+error:"invalid_request",
+error_description:"RedirectLoginServiceProvider.OAuthAuthorizeURI() not set"
+});
+if ("" === c) return e.reject({
+error:"invalid_request",
+error_description:"RedirectLoginServiceProvider.OAuthRedirectURI not set"
+});
+var f = e.defer(), g = new URI(b), i = new URI(d.url()).fragment("");
+return g.query({
+client_id:a,
+response_type:"token",
+state:k(i.toString()),
+redirect_uri:c
+}), h.log("RedirectLoginService.login(), redirecting", g.toString()), window.location.href = g.toString(), f.promise;
+},
+finish:function() {
+var a = new URI(d.url()), b = a.query(!0), c = new URI("?" + a.fragment()).query(!0);
+h.log("RedirectLoginService.finish()", b, c);
+var f = b.error || c.error;
+if (f) {
+var g = b.error_description || c.error_description, i = b.error_uri || c.error_uri;
+return h.log("RedirectLoginService.finish(), error", f, g, i), e.reject({
+error:f,
+error_description:g,
+error_uri:i
+});
+}
+var j = l(c.state);
+if (c.access_token && "bearer" === (c.token_type || "").toLowerCase()) {
+var k = e.defer();
+return k.resolve({
+token:c.access_token,
+ttl:c.expires_in,
+then:j.then,
+verified:j.verified
+}), k.promise;
+}
+return e.reject({
+error:"invalid_request",
+error_description:"No API token returned"
+});
+}
+};
+} ];
+}), angular.module("openshiftCommon").provider("$ws", [ "$httpProvider", function(a) {
+this.$get = [ "$q", "$injector", "Logger", function(b, c, d) {
+var e = d.get("auth");
+e.log("$wsProvider.$get", arguments);
+var f = [];
+angular.forEach(a.interceptors, function(a) {
+angular.isString(a) ? f.unshift(c.get(a)) :f.unshift(c.invoke(a));
+});
+var g = function(a) {
+a.method = angular.uppercase(a.method || "WATCH"), e.log("$ws (pre-intercept)", a.url.toString());
+var c = function(a) {
+e.log("$ws (post-intercept)", a.url.toString());
+var b = new WebSocket(a.url, a.protocols);
+return a.onclose && (b.onclose = a.onclose), a.onmessage && (b.onmessage = a.onmessage), a.onopen && (b.onopen = a.onopen), a.onerror && (b.onerror = a.onerror), b;
+}, d = [ c, void 0 ], g = b.when(a);
+for (angular.forEach(f, function(a) {
+(a.request || a.requestError) && d.unshift(a.request, a.requestError);
+}); d.length; ) {
+var h = d.shift(), i = d.shift();
+g = g.then(h, i);
+}
+return g;
+};
+return g.available = function() {
+try {
+return !!WebSocket;
+} catch (a) {
+return !1;
+}
+}, g;
+} ];
+} ]);
\ No newline at end of file
diff --git a/dist/styles/main.css b/dist/styles/main.css
index fb3ad1d85b..1915082d7f 100644
--- a/dist/styles/main.css
+++ b/dist/styles/main.css
@@ -2520,7 +2520,7 @@ select.bs-select-hidden,select.selectpicker{display:none!important}
.c3-chart-arc .c3-gauge-value{fill:#000}
/*!
* Datetimepicker for Bootstrap 3
- * version : 4.17.45
+ * version : 4.17.47
* https://github.com/Eonasdan/bootstrap-datetimepicker/
*/
.bootstrap-datetimepicker-widget{list-style:none}
diff --git a/test/karma.conf.js b/test/karma.conf.js
index b894db9296..e7d8ff928d 100644
--- a/test/karma.conf.js
+++ b/test/karma.conf.js
@@ -59,6 +59,7 @@ module.exports = function(config) {
"bower_components/angular-moment/angular-moment.js",
"bower_components/angular-utf8-base64/angular-utf8-base64.js",
'app/config.js',
+ "bower_components/origin-web-common/dist/origin-web-common.js",
'app/scripts/**/*.js',
'app/views/directives/**/*.html',
//'test/mock/**/*.js',
diff --git a/test/spec/services/apiServiceSpec.js b/test/spec/services/apiServiceSpec.js
deleted file mode 100644
index 8484000b42..0000000000
--- a/test/spec/services/apiServiceSpec.js
+++ /dev/null
@@ -1,240 +0,0 @@
-"use strict";
-
-describe("APIService", function(){
- var APIService;
-
- beforeEach(function(){
- inject(function(_APIService_){
- APIService = _APIService_;
- });
- });
-
- describe("#toResourceGroupVersion", function(){
-
- var tc = [
- // string args
- // simple
- ['pods', {r:'pods',g:'',v:'v1'}],
- // normalization
- ['Pods', {r:'pods',g:'',v:'v1'}],
- // normalization preserves subresources
- ['PODS/FOO', {r:'pods/FOO',g:'',v:'v1'}],
-
- // structured, resource only
- // simple
- [{resource:'pods'}, {r:'pods',g:'',v:'v1'}],
- // normalization
- [{resource:'Pods'}, {r:'pods',g:'',v:'v1'}],
- // normalization preserves subresources
- [{resource:'PODS/FOO'}, {r:'pods/FOO',g:'',v:'v1'}],
-
- // structured, with group
- // groups default version if known
- [{resource:'pods',group:''}, {r:'pods',g:'', v:'v1' }],
- [{resource:'jobs',group:'extensions'}, {r:'jobs',g:'extensions',v:'v1beta1'}],
- // unknown groups do not default version
- [{resource:'foos',group:'unknown'}, {r:'foos',g:'unknown', v:undefined}],
-
- // structured, with version
- // groups default
- [{resource:'pods',version:'v1'}, {r:'pods',g:'',v:'v1' }],
- [{resource:'pods',version:'v1beta3'}, {r:'pods',g:'',v:'v1beta3'}],
-
- // structured, fully specified
- [{resource:'pods',group:'', version:'v1'}, {r:'pods',g:'', v:'v1' }],
- [{resource:'pods',group:'', version:'v1beta3'}, {r:'pods',g:'', v:'v1beta3'}],
- [{resource:'jobs',group:'extensions',version:'v1'}, {r:'jobs',g:'extensions',v:'v1' }],
- [{resource:'jobs',group:'extensions',version:'v1beta1'}, {r:'jobs',g:'extensions',v:'v1beta1'}],
- [{resource:'foos',group:'unknown', version:'v1'}, {r:'foos',g:'unknown', v:'v1' }],
- [{resource:'foos',group:'unknown', version:'v1beta1'}, {r:'foos',g:'unknown', v:'v1beta1'}]
- ];
-
- angular.forEach(tc, _.spread(function(input, expectedRGV) {
- it('should result in ' + JSON.stringify(expectedRGV) + ' when called with ' + JSON.stringify(input), function() {
- // Call once and compare the components
- var actualRGV = APIService.toResourceGroupVersion(input);
- expect(actualRGV.resource).toEqual(expectedRGV.r);
- expect(actualRGV.group ).toEqual(expectedRGV.g);
- expect(actualRGV.version ).toEqual(expectedRGV.v);
-
- // Call again with the result and make sure it is returns the same thing
- var actualRGV2 = APIService.toResourceGroupVersion(actualRGV);
- expect(actualRGV).toEqual(actualRGV2);
- });
- }));
-
- });
-
- describe("#parseGroupVersion", function(){
- var tc = [
- // invalid cases
- [null, undefined],
- ["", undefined],
- ['foo/bar/baz', undefined],
- // legacy
- ['v1', {group:'',version:'v1' }],
- // groups
- ['foo/bar', {group:'foo',version:'bar'}],
- ['FOO/BAR', {group:'FOO',version:'BAR'}],
- // group missing version, we see this on events
- ['apps', {group:'apps',version:''}],
- ];
- angular.forEach(tc, _.spread(function(input, expectedGroupVersion) {
- it('should result in ' + JSON.stringify(expectedGroupVersion) + ' when called with ' + JSON.stringify(input), function() {
- expect(APIService.parseGroupVersion(input)).toEqual(expectedGroupVersion);
- });
- }));
- });
-
- describe("#objectToResourceGroupVersion", function(){
- var tc = [
- // invalid cases
- [null, undefined],
- ["", undefined],
- [{}, undefined],
- [{kind:"Pod"}, undefined],
- [{apiVersion:"v1"}, undefined],
-
- // legacy
- [{kind:"Pod", apiVersion:"v1"}, {g:'',v:'v1',r:'pods'}],
-
- // extensions
- [{kind:"Job",apiVersion:"extensions/v1beta1"}, {g:'extensions',v:'v1beta1',r:'jobs'}],
- [{kind:"Foo",apiVersion:"unknown/v1beta6"}, {g:'unknown', v:'v1beta6',r:'foos'}],
- ];
- angular.forEach(tc, _.spread(function(input, expectedRGV) {
- it('should result in ' + JSON.stringify(expectedRGV) + ' when called with ' + JSON.stringify(input), function() {
- // Call once and compare the components
- var actualRGV = APIService.objectToResourceGroupVersion(input);
- if (expectedRGV) {
- expect(actualRGV.resource).toEqual(expectedRGV.r);
- expect(actualRGV.group ).toEqual(expectedRGV.g);
- expect(actualRGV.version ).toEqual(expectedRGV.v);
- } else {
- expect(actualRGV).toEqual(expectedRGV);
- }
- });
- }));
- });
-
- describe("#kindToResource", function(){
- var tc = [
- // invalid cases
- [null, ""],
- ["", ""],
-
- // pluralization
- ["foo", "foos"],
- // pluralization with s
- ["foos", "fooses"],
- // pluralization with y
- ["Policy", "policies"],
- // special cases
- ["Endpoints", "endpoints"],
- ["SecurityContextConstraints", "securitycontextconstraints"],
- ];
- angular.forEach(tc, _.spread(function(kind, resource) {
- it('should result in ' + JSON.stringify(resource) + ' when called with ' + JSON.stringify(kind), function() {
- expect(APIService.kindToResource(kind)).toEqual(resource);
- });
- }));
- });
-
- describe("#deriveTargetResource", function(){
- var tc = [
- // invalid cases
- [null,null, undefined],
- ["","", undefined],
- [{},{}, undefined],
-
- // simple resource, matching object overrides group/version
- ['pods', {kind:"Pod",apiVersion:"v1"}, {r:'pods',g:'', v:'v1' }],
- ['pods', {kind:"Pod",apiVersion:"extensions"}, {r:'pods',g:'extensions',v:'' }],
- ['jobs', {kind:"Job",apiVersion:"extensions/v1beta1"}, {r:'jobs',g:'extensions',v:'v1beta1'}],
- ['jobs', {kind:"Job",apiVersion:"extensions/v1beta2"}, {r:'jobs',g:'extensions',v:'v1beta2'}],
-
- // simple resource, non-matching object leaves group/version alone
- ['pods', {kind:"Foo",apiVersion:"v1"}, {r:'pods',g:'',v:'v1'}],
- ['pods', {kind:"Foo",apiVersion:"v2"}, {r:'pods',g:'',v:'v1'}],
- ['jobs', {kind:"Foo",apiVersion:"extensions/v1beta1"}, {r:'jobs',g:'',v:'v1'}],
- ['jobs', {kind:"Foo",apiVersion:"extensions/v1beta2"}, {r:'jobs',g:'',v:'v1'}],
- // actual use:
- ['deploymentconfigs/scale', {kind:"Scale",apiVersion:"extensions/v1beta1"}, {r:'deploymentconfigs/scale',g:'',v:'v1'}],
-
- // complex resource, matching object kind and group overrides version
- [{resource:'pods',group:'' }, {kind:"Pod",apiVersion:"v1"}, {r:'pods',g:'', v:'v1' }],
- [{resource:'pods',group:'', version:'v2'}, {kind:"Pod",apiVersion:"v1"}, {r:'pods',g:'', v:'v1' }],
- [{resource:'jobs',group:'extensions' }, {kind:"Job",apiVersion:"extensions/v1beta3"}, {r:'jobs',g:'extensions',v:'v1beta3'}],
- [{resource:'jobs',group:'extensions', version:'v2'}, {kind:"Job",apiVersion:"extensions/v1beta3"}, {r:'jobs',g:'extensions',v:'v1beta3'}],
-
- // complex resource, non-matching object group leaves group/version alone
- [{resource:'pods', }, {kind:"Pod",apiVersion:"othergroup/v3"}, {r:'pods',g:'', v:'v1' }],
- [{resource:'pods',group:'' }, {kind:"Pod",apiVersion:"othergroup/v3"}, {r:'pods',g:'', v:'v1' }],
- [{resource:'pods',group:'', version:'v2'}, {kind:"Pod",apiVersion:"othergroup/v3"}, {r:'pods',g:'', v:'v2' }],
- [{resource:'jobs',group:'extensions' }, {kind:"Job",apiVersion:"othergroup/v1beta3"}, {r:'jobs',g:'extensions',v:'v1beta1'}],
- [{resource:'jobs',group:'extensions', version:'v2'}, {kind:"Job",apiVersion:"othergroup/v1beta3"}, {r:'jobs',g:'extensions',v:'v2' }],
- // complex resource, non-matching object kind leaves group/version alone
- [{resource:'pods',group:'' }, {kind:"Foo",apiVersion:"v3"}, {r:'pods',g:'', v:'v1' }],
- [{resource:'pods',group:'', version:'v2'}, {kind:"Foo",apiVersion:"v3"}, {r:'pods',g:'', v:'v2' }],
- // actual use:
- [{resource:'deploymentconfigs/scale'}, {kind:"Scale",apiVersion:"extensions/v1beta1"}, {r:'deploymentconfigs/scale',g:'',v:'v1'}],
- ];
- angular.forEach(tc, _.spread(function(resource, apiObject, expectedRGV) {
- it('should result in ' + JSON.stringify(expectedRGV) + ' when called with ' + JSON.stringify(resource)+","+JSON.stringify(apiObject), function() {
- // Call once and compare the components
- var actualRGV = APIService.deriveTargetResource(resource, apiObject);
- if (expectedRGV) {
- expect(actualRGV.resource).toEqual(expectedRGV.r);
- expect(actualRGV.group ).toEqual(expectedRGV.g);
- expect(actualRGV.version ).toEqual(expectedRGV.v);
- } else {
- expect(actualRGV).toEqual(expectedRGV);
- }
- });
- }));
- });
-
- describe("#primaryResource", function(){
- var tc = [
- // invalid cases
- [null, ""],
- ["", ""],
-
- // no subresources
- ["foo", "foo"],
- ["FOO", "foo"],
-
- // subresource cases
- ["foo/bar", "foo"],
- ["FOO/bar/baz", "foo"]
- ];
- angular.forEach(tc, _.spread(function(resource, primaryResource) {
- it('should result in ' + JSON.stringify(primaryResource) + ' when called with ' + JSON.stringify(resource), function() {
- expect(APIService.toResourceGroupVersion(resource).primaryResource()).toEqual(primaryResource);
- });
- }));
- });
-
- describe("#subresources", function(){
- var tc = [
- // invalid cases
- [null, []],
- ["", []],
-
- // no subresources
- ["foo", []],
- ["FOO", []],
-
- // subresource cases
- ["foo/bar", ["bar"]],
- ["FOO/bar/baz", ["bar","baz"]],
- ["FOO/Bar/Baz", ["Bar","Baz"]]
- ];
- angular.forEach(tc, _.spread(function(resource, subresources) {
- it('should result in ' + JSON.stringify(subresources) + ' when called with ' + JSON.stringify(resource), function() {
- expect(APIService.toResourceGroupVersion(resource).subresources()).toEqual(subresources);
- });
- }));
- });
-
-});
diff --git a/test/spec/services/dataServiceSpec.js b/test/spec/services/dataServiceSpec.js
deleted file mode 100644
index 0d2dedaeca..0000000000
--- a/test/spec/services/dataServiceSpec.js
+++ /dev/null
@@ -1,99 +0,0 @@
-"use strict";
-
-describe("DataService", function(){
- var DataService;
-
- beforeEach(function(){
- inject(function(_DataService_){
- DataService = _DataService_;
- });
- });
-
- describe("#url", function(){
-
- var tc = [
- // Empty tests
- [null, null],
- [{}, null],
-
- // Unknown resources
- [{resource:''}, null],
- [{resource:'bogus'}, null],
-
- // Kind is not allowed
- [{resource:'Pod'}, null],
- [{resource:'User'}, null],
-
- // resource normalization
- [{resource:'users'}, "http://localhost:8443/oapi/v1/users"],
- [{resource:'Users'}, "http://localhost:8443/oapi/v1/users"],
- [{resource:'oauthaccesstokens'}, "http://localhost:8443/oapi/v1/oauthaccesstokens"],
- [{resource:'OAuthAccessTokens'}, "http://localhost:8443/oapi/v1/oauthaccesstokens"],
- [{resource:'pods'}, "http://localhost:8443/api/v1/pods"],
- [{resource:'Pods'}, "http://localhost:8443/api/v1/pods"],
-
- // Openshift resource
- [{resource:'builds' }, "http://localhost:8443/oapi/v1/builds"],
- [{resource:'builds', namespace:"foo" }, "http://localhost:8443/oapi/v1/namespaces/foo/builds"],
- [{resource:'builds', name:"bar"}, "http://localhost:8443/oapi/v1/builds/bar"],
- [{resource:'builds', namespace:"foo", name:"bar"}, "http://localhost:8443/oapi/v1/namespaces/foo/builds/bar"],
-
- // k8s resource
- [{resource:'replicationcontrollers' }, "http://localhost:8443/api/v1/replicationcontrollers"],
- [{resource:'replicationcontrollers', namespace:"foo" }, "http://localhost:8443/api/v1/namespaces/foo/replicationcontrollers"],
- [{resource:'replicationcontrollers', name:"bar"}, "http://localhost:8443/api/v1/replicationcontrollers/bar"],
- [{resource:'replicationcontrollers', namespace:"foo", name:"bar"}, "http://localhost:8443/api/v1/namespaces/foo/replicationcontrollers/bar"],
-
- // Subresources and webhooks
- [{resource:'pods/proxy', name:"mypod:1123", namespace:"foo"}, "http://localhost:8443/api/v1/namespaces/foo/pods/mypod%3A1123/proxy"],
- [{resource:'builds/clone', name:"mybuild", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/builds/mybuild/clone"],
- [{resource:'buildconfigs/instantiate', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/instantiate"],
- [{resource:'buildconfigs/webhooks/123/github', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/webhooks/123/github"],
- [{resource:'buildconfigs/webhooks/123?234/github', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/webhooks/123%3F234/github"],
- // Subresources aren't lowercased
- [{resource:'buildconfigs/webhooks/Aa1/github', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/webhooks/Aa1/github"],
-
-
-
- // Plain websocket
- [{resource:'pods', namespace:"foo", isWebsocket:true }, "ws://localhost:8443/api/v1/namespaces/foo/pods"],
-
- // Watch resource
- [{resource:'pods', namespace:"foo", isWebsocket:true, watch: true }, "ws://localhost:8443/api/v1/namespaces/foo/pods?watch=true"],
- [{resource:'pods', namespace:"foo", isWebsocket:true, watch: true, resourceVersion:"5" }, "ws://localhost:8443/api/v1/namespaces/foo/pods?watch=true&resourceVersion=5"],
-
- // Follow log
- // subresource is ignored without a resource name
- [{resource:'pods/log', namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/api/v1/namespaces/foo/pods?follow=true"],
- [{resource:'builds/log', namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/oapi/v1/namespaces/foo/builds?follow=true"],
- // subresource is honored with a resource name
- [{resource:'pods/log', name:"p", namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/api/v1/namespaces/foo/pods/p/log?follow=true"],
- [{resource:'builds/log', name:"b", namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/oapi/v1/namespaces/foo/builds/b/log?follow=true"],
-
-
-
- // Namespaced subresource with params
- [{resource:'pods/proxy', name:"mypod", namespace:"myns", myparam1:"myvalue"}, "http://localhost:8443/api/v1/namespaces/myns/pods/mypod/proxy?myparam1=myvalue"],
-
- // Different API versions
- [{resource:'builds',version:'v1beta3'}, null],
- [{resource:'builds',version:'v1' }, "http://localhost:8443/oapi/v1/builds"],
- [{resource:'builds',version:'unknown'}, null],
-
- [{resource:'pods', version:'v1beta3'}, null],
- [{resource:'pods', version:'v1' }, "http://localhost:8443/api/v1/pods"],
- [{resource:'pods', version:'unknown'}, null],
-
- // Different API groups
- [{resource:'jobs', group: 'extensions', version:'v1beta1'}, "http://localhost:8443/apis/extensions/v1beta1/jobs"]
- ];
-
- angular.forEach(tc, function(item) {
- it('should generate a correct URL for ' + JSON.stringify(item[0]), function() {
- expect(DataService.url(item[0])).toEqual(item[1]);
- });
- });
-
- });
-
-});