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]); - }); - }); - - }); - -});