This closes #121
diff --git a/modularity-server/metadata-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/modularity-server/metadata-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index ca92875..3c0293a 100644
--- a/modularity-server/metadata-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/modularity-server/metadata-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -34,7 +34,36 @@
</cm:default-properties>
</cm:property-placeholder>
+ <reference id="localManagementContext"
+ interface="org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal"/>
+ <reference id="shutdownHandler" interface="org.apache.brooklyn.core.mgmt.ShutdownHandler"/>
+
+ <cm:property-placeholder persistent-id="org.apache.brooklyn.rest.filter.cors" placeholder-prefix="$cors{">
+ <!-- since we have properties above for this bundle we need a unique prefix/suffix here -->
+ <cm:default-properties>
+ <cm:property name="cors.enabled" value="true"/>
+ <cm:property name="cors.allow.origins" value=""/>
+ <cm:property name="cors.allow.headers" value=""/>
+ <cm:property name="cors.expose.headers" value=""/>
+ <cm:property name="cors.allow.credentials" value="true"/>
+ <cm:property name="cors.max.age" value="-1"/>
+ <cm:property name="cors.preflight.error.status" value="200"/>
+ <cm:property name="cors.block.if.unauthorized" value="false"/>
+ </cm:default-properties>
+ </cm:property-placeholder>
+
+ <bean class="org.apache.brooklyn.rest.filter.CorsImplSupplierFilter" id="cors-filter">
+ <property name="enableCors" value="$cors{cors.enabled}"/>
+ <property name="allowOrigins" value="$cors{cors.allow.origins}"/>
+ <property name="allowHeaders" value="$cors{cors.allow.headers}"/>
+ <property name="exposeHeaders" value="$cors{cors.expose.headers}"/>
+ <property name="allowCredentials" value="$cors{cors.allow.credentials}"/>
+ <property name="maxAge" value="$cors{cors.max.age}"/>
+ <property name="preflightErrorStatus" value="$cors{cors.preflight.error.status}"/>
+ <property name="blockCorsIfUnauthorized" value="$cors{cors.block.if.unauthorized}"/>
+ </bean>
+
<bean id="registry" class="org.apache.brooklyn.ui.modularity.metadata.registry.impl.UiMetadataRegistryImpl"/>
<service id="uiMetadataRegistryService" ref="registry" interface="org.apache.brooklyn.ui.modularity.metadata.registry.UiMetadataRegistry"/>
@@ -45,14 +74,30 @@
<property name="metadataRegistry" ref="registry"/>
</bean>
</jaxrs:serviceBeans>
- <jaxrs:inInterceptors>
- <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
- </jaxrs:inInterceptors>
- <jaxrs:outInterceptors>
- <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
- </jaxrs:outInterceptors>
+
<jaxrs:providers>
- <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
+ <bean class="org.apache.brooklyn.rest.util.DefaultExceptionMapper"/>
+ <bean class="org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider"/>
+ <bean class="org.apache.brooklyn.rest.util.FormMapProvider"/>
+ <bean class="org.apache.brooklyn.rest.util.ManagementContextProvider">
+ <argument ref="localManagementContext"/>
+ </bean>
+ <bean class="org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterJersey"/>
+ <bean class="org.apache.brooklyn.rest.filter.CsrfTokenFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.RequestTaggingRsFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.NoCacheFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.EntitlementContextFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.LoggingResourceFilter"/>
+ <bean class="io.swagger.jaxrs.listing.SwaggerSerializers"/>
+ <bean class="org.apache.brooklyn.rest.util.ShutdownHandlerProvider">
+ <argument ref="shutdownHandler"/>
+ </bean>
+ <ref component-id="cors-filter"/>
</jaxrs:providers>
+
+ <jaxrs:properties>
+ <entry key="default.wae.mapper.least.specific" value="true"/>
+ </jaxrs:properties>
</jaxrs:server>
</blueprint>
diff --git a/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/UiModule.java b/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/UiModule.java
index 43bdeb2..e50d75b 100644
--- a/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/UiModule.java
+++ b/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/UiModule.java
@@ -21,6 +21,8 @@
import java.util.List;
import java.util.Set;
+import org.apache.brooklyn.ui.modularity.module.api.internal.UiModuleImpl;
+
public interface UiModule {
String DEFAULT_ICON = "fa-cogs";
@@ -69,4 +71,10 @@
* @return Registered module actions
*/
List<UiModuleAction> getActions();
+
+ public class Utils {
+ public static UiModule copyUiModule(UiModule src) {
+ return UiModuleImpl.copyOf(src);
+ }
+ }
}
diff --git a/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/internal/UiModuleImpl.java b/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/internal/UiModuleImpl.java
index 845cc8a..7d62fb1 100644
--- a/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/internal/UiModuleImpl.java
+++ b/modularity-server/module-api/src/main/java/org/apache/brooklyn/ui/modularity/module/api/internal/UiModuleImpl.java
@@ -41,6 +41,20 @@
private String path;
private List<UiModuleAction> actions = new ArrayList<>();
+ public static UiModuleImpl copyOf(UiModule src) {
+ final UiModuleImpl result = new UiModuleImpl();
+ result.setId(src.getId());
+ result.setName(src.getName());
+ result.setSlug(src.getSlug());
+ result.setIcon(src.getIcon());
+ if (src.getTypes()!=null) result.types.addAll(src.getTypes());
+ if (src.getSupersedesBundles()!=null) result.supersedesBundles.addAll(src.getSupersedesBundles());
+ result.setStopExisting(src.getStopExisting());
+ result.setPath(src.getPath());
+ if (src.getActions()!=null) result.actions.addAll(src.getActions());
+ return result;
+ }
+
public static UiModuleImpl createFromMap(final Map<String, ?> incomingMap) {
final UiModuleImpl result = new UiModuleImpl();
result.setId(Optional.fromNullable((String) incomingMap.get("id")).or(UUID.randomUUID().toString()));
diff --git a/modularity-server/module-registry/src/main/java/org/apache/brooklyn/ui/modularity/module/registry/RestUiModuleRegistry.java b/modularity-server/module-registry/src/main/java/org/apache/brooklyn/ui/modularity/module/registry/RestUiModuleRegistry.java
index 5aca5b5..86a2350 100644
--- a/modularity-server/module-registry/src/main/java/org/apache/brooklyn/ui/modularity/module/registry/RestUiModuleRegistry.java
+++ b/modularity-server/module-registry/src/main/java/org/apache/brooklyn/ui/modularity/module/registry/RestUiModuleRegistry.java
@@ -18,17 +18,20 @@
*/
package org.apache.brooklyn.ui.modularity.module.registry;
-import com.google.common.base.Function;
-import com.google.common.collect.Ordering;
-import org.apache.brooklyn.ui.modularity.module.api.UiModule;
-import org.apache.brooklyn.ui.modularity.module.api.UiModuleRegistry;
+import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
-import java.util.Collection;
+
+import org.apache.brooklyn.ui.modularity.module.api.UiModule;
+import org.apache.brooklyn.ui.modularity.module.api.UiModuleRegistry;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Ordering;
@Path("/")
public class RestUiModuleRegistry {
@@ -50,7 +53,9 @@
public Collection<UiModule> getRegisteredWebComponents() {
return Ordering.natural()
.onResultOf(GET_NAME_FUNCTION)
- .immutableSortedCopy(uiModuleRegistry.getRegisteredModules());
+ .immutableSortedCopy(
+ // turn it from a proxy to a serializable bean
+ Iterables.transform(uiModuleRegistry.getRegisteredModules(), x -> UiModule.Utils.copyUiModule(x)));
}
public void setUiModuleRegistry(final UiModuleRegistry uiModuleRegistry) {
diff --git a/modularity-server/module-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/modularity-server/module-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 9d718a1..9cd0351 100644
--- a/modularity-server/module-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/modularity-server/module-registry/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -36,7 +36,6 @@
</cm:default-properties>
</cm:property-placeholder>
-
<bean id="servlet" class="org.apache.brooklyn.ui.modularity.module.registry.internal.RedirectServlet">
<argument index="0" type="java.lang.String" value="${redirect.path}"/>
</bean>
@@ -46,6 +45,36 @@
</service-properties>
</service>
+ <reference id="localManagementContext"
+ interface="org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal"/>
+
+ <reference id="shutdownHandler" interface="org.apache.brooklyn.core.mgmt.ShutdownHandler"/>
+
+ <cm:property-placeholder persistent-id="org.apache.brooklyn.rest.filter.cors" placeholder-prefix="$cors{">
+ <!-- since we have properties above for this bundle we need a unique prefix/suffix here -->
+ <cm:default-properties>
+ <cm:property name="cors.enabled" value="true"/>
+ <cm:property name="cors.allow.origins" value=""/>
+ <cm:property name="cors.allow.headers" value=""/>
+ <cm:property name="cors.expose.headers" value=""/>
+ <cm:property name="cors.allow.credentials" value="true"/>
+ <cm:property name="cors.max.age" value="-1"/>
+ <cm:property name="cors.preflight.error.status" value="200"/>
+ <cm:property name="cors.block.if.unauthorized" value="false"/>
+ </cm:default-properties>
+ </cm:property-placeholder>
+
+ <bean class="org.apache.brooklyn.rest.filter.CorsImplSupplierFilter" id="cors-filter">
+ <property name="enableCors" value="$cors{cors.enabled}"/>
+ <property name="allowOrigins" value="$cors{cors.allow.origins}"/>
+ <property name="allowHeaders" value="$cors{cors.allow.headers}"/>
+ <property name="exposeHeaders" value="$cors{cors.expose.headers}"/>
+ <property name="allowCredentials" value="$cors{cors.allow.credentials}"/>
+ <property name="maxAge" value="$cors{cors.max.age}"/>
+ <property name="preflightErrorStatus" value="$cors{cors.preflight.error.status}"/>
+ <property name="blockCorsIfUnauthorized" value="$cors{cors.block.if.unauthorized}"/>
+ </bean>
+
<bean id="module-registry"
class="org.apache.brooklyn.ui.modularity.module.registry.UiModuleRegistryImpl"/>
<reference-list interface="org.apache.brooklyn.ui.modularity.module.api.UiModule" availability="optional">
@@ -54,20 +83,36 @@
<service interface="org.apache.brooklyn.ui.modularity.module.api.UiModuleRegistry" ref="module-registry"/>
- <jaxrs:server id="brooklynRestApiV1" address="${ui.module.api.path}">
+ <jaxrs:server id="brooklynRestApiV1UiModuleRegistry" address="${ui.module.api.path}">
<jaxrs:serviceBeans>
<bean class="org.apache.brooklyn.ui.modularity.module.registry.RestUiModuleRegistry">
<property name="uiModuleRegistry" ref="module-registry"/>
</bean>
</jaxrs:serviceBeans>
- <jaxrs:inInterceptors>
- <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
- </jaxrs:inInterceptors>
- <jaxrs:outInterceptors>
- <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
- </jaxrs:outInterceptors>
+
<jaxrs:providers>
- <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
+ <bean class="org.apache.brooklyn.rest.util.DefaultExceptionMapper"/>
+ <bean class="org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider"/>
+ <bean class="org.apache.brooklyn.rest.util.FormMapProvider"/>
+ <bean class="org.apache.brooklyn.rest.util.ManagementContextProvider">
+ <argument ref="localManagementContext"/>
+ </bean>
+ <bean class="org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterJersey"/>
+ <bean class="org.apache.brooklyn.rest.filter.CsrfTokenFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.RequestTaggingRsFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.NoCacheFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.EntitlementContextFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.LoggingResourceFilter"/>
+ <bean class="io.swagger.jaxrs.listing.SwaggerSerializers"/>
+ <bean class="org.apache.brooklyn.rest.util.ShutdownHandlerProvider">
+ <argument ref="shutdownHandler"/>
+ </bean>
+ <ref component-id="cors-filter"/>
</jaxrs:providers>
+
+ <jaxrs:properties>
+ <entry key="default.wae.mapper.least.specific" value="true"/>
+ </jaxrs:properties>
</jaxrs:server>
</blueprint>
diff --git a/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js b/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js
index aff3f62..5c30829 100644
--- a/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js
+++ b/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js
@@ -150,7 +150,7 @@
vm.error.policies = 'Cannot load policies for entity with ID: ' + entityId;
});
- $http.get('v1/ui-metadata-registry', {params: {type: 'location'}}).then(response => {
+ $http.get('/v1/ui-metadata-registry', {params: {type: 'location'}}).then(response => {
vm.metadata = response.data;
});
diff --git a/ui-modules/logout/Makefile b/ui-modules/logout/Makefile
new file mode 100644
index 0000000..c7cb424
--- /dev/null
+++ b/ui-modules/logout/Makefile
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+default: dev
+
+build:
+ @echo "Building production bundle..."
+ NODE_ENV="production" npm run build
+
+clean:
+ @echo "Cleaning modules..."
+ @rm -rf ./node_modules
+
+dev:
+ @echo "Starting dev web server..."
+ @npm start
+
+server: build
+ @echo "Starting api proxy server..."
+ NODE_ENV="production" npm start
+
+install:
+ @echo "Installing npm modules..."
+ @npm install
+
+test:
+ @echo "Running tests..."
+ @npm test
+
+setup: clean install
+
+war:
+ @mvn clean install
+
+.PHONY: build clean deploy dev install server setup test war
diff --git a/ui-modules/logout/app/index.js b/ui-modules/logout/app/index.js
index 09f6e4f..e5d4d15 100644
--- a/ui-modules/logout/app/index.js
+++ b/ui-modules/logout/app/index.js
@@ -26,10 +26,11 @@
import brInterstitialSpinner from 'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner';
import brooklynModuleLinks from 'brooklyn-ui-utils/module-links/module-links';
import brooklynUserManagement from 'brooklyn-ui-utils/user-management/user-management';
+import brServerStatus from 'brooklyn-ui-utils/server-status/server-status';
import mainState from './views/main/main.controller';
-angular.module('app', [ngAnimate, ngCookies, uiRouter, brCore, brInterstitialSpinner, brooklynModuleLinks, brooklynUserManagement, mainState])
+angular.module('app', [ngAnimate, ngCookies, uiRouter, brCore, brInterstitialSpinner, brServerStatus, brooklynModuleLinks, brooklynUserManagement, mainState])
.config(['$urlRouterProvider', '$logProvider', applicationConfig])
.run(['$http', httpConfig]);
diff --git a/ui-modules/logout/app/index.less b/ui-modules/logout/app/index.less
index 5750772..1f64564 100644
--- a/ui-modules/logout/app/index.less
+++ b/ui-modules/logout/app/index.less
@@ -19,7 +19,7 @@
@import '~brooklyn-shared/style/first.less';
// Add project less files here
-
+.text-narrow > div { margin-left: 25%; margin-right: 25%; }
// Load last so that these style rules and var values trump others
@import "~brooklyn-shared/style/last.less";
diff --git a/ui-modules/logout/app/views/main/main.controller.js b/ui-modules/logout/app/views/main/main.controller.js
index 8e554fb..7fda72f 100644
--- a/ui-modules/logout/app/views/main/main.controller.js
+++ b/ui-modules/logout/app/views/main/main.controller.js
@@ -28,51 +28,123 @@
export default MODULE_NAME;
export const mainState = {
- name: 'main',
- url: '/',
+ name: 'mainRoot',
+ url: '/?debug&keepCreds&useGet&salt',
+ // experimental/test options:
+ // * useGet means to make a GET request instead of POST
+ // * keepCreds means not to request a 200 on successful logout instead of a 401;
+ // this will prevent the browser from clearing cache
template: require('ejs-html!./main.template.html'),
- controller: ['$scope', mainStateController],
+ controller: ['$scope', '$http', '$state', '$stateParams', '$log', '$timeout', mainStateController],
+ controllerAs: 'vm'
+};
+export const promptState = {
+ name: 'prompt',
+ url: '/prompt?debug',
+ params: { prompt: true },
+ template: require('ejs-html!./main.template.html'),
+ controller: ['$scope', '$http', '$state', '$stateParams', mainStateController],
controllerAs: 'vm'
};
export function mainStateConfig($stateProvider) {
- $stateProvider.state(mainState);
+ $stateProvider.state(promptState).state(mainState);
}
-export function mainStateController($scope) {
+export function mainStateController($scope, $http, $state, $stateParams, $log, $timeout) {
+ if (!$scope.state) $scope.state = {};
+ if ($stateParams.prompt) $scope.state.status = "prompt";
+ if (!$scope.state.status) $scope.state.status = "do-logout";
+
+ /* There is a lot of complexity in here to support debug pathways with confirmation,
+ * use of http GET instead of POST, and use of API which returns 200 instead of 401.
+ * This is because logging out nicely is quite tricky.
+ * Currently we think we have a good pathway without any of that complexity,
+ * so if you haven't set "?debug=true" or other special option in the URL it is
+ * mostly disabled and follows the happy path where it just logs out and prompts
+ * you to log back in. But the debug stuff is left in, in case we encounter edge cases.
+ */
+
+ $scope.debug = $stateParams.debug;
+ if ($scope.debug) {
+ $log.info("Logout page running in debug mode. state=", $state, "state params=", $stateParams);
+ }
+ if ($stateParams.salt) {
+ // specify some salt to ensure links change in dev mode
+ $scope.salt = (parseInt($stateParams.salt) || 0);
+ }
+
$scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
- let userRequest = new XMLHttpRequest();
- userRequest.onreadystatechange = function () {
- if (this.readyState === 4 && this.status === 200) {
- logout(this.responseText)
- }
- };
- userRequest.open('GET', '/v1/server/user', true);
- userRequest.send('');
-
- /**
- * Logout the supplied user
- * @param user
- */
- function logout(user) {
+ function clearLocalCache() {
let ua = window.navigator.userAgent;
if (ua.indexOf('MSIE ') >= 0 || ua.indexOf(' Edge/') >= 0 || ua.indexOf(' Trident/') >= 0) {
document.execCommand('ClearAuthenticationCache', 'false');
}
- let logoutRequest = new XMLHttpRequest();
- logoutRequest.onreadystatechange = function () {
- if (this.readyState === 4) {
- if (this.status === 401) {
- console.info('User ' + user + ' logged out')
- } else {
- setTimeout(function () {
- logout(user);
- }, 1000);
- }
- }
- };
- logoutRequest.open('POST', '/v1/logout', true, user, Math.random().toString(36).slice(2));
- logoutRequest.send('');
}
+
+ function handleError(phase, response, expectAlreadyLoggedOut) {
+ if (response && response.status >= 300 && response.status < 400 || response.status == 401) {
+ // auth required
+ if (expectAlreadyLoggedOut) {
+ $scope.state = { status: "logout-confirmed", code: response.status };
+ } else {
+ $scope.state = { status: "already-logged-out", code: response.status };
+ }
+ } else if (response && response.status && response.status>0) {
+ $log.warn("Server failure "+phase, response);
+ $scope.state = { status: "failed", message: "server failure ("+response.status+") "+phase+
+ (response.message ? ": "+response.message : ""), code: response.status };
+ } else {
+ $log.info("Connection failure "+phase, response);
+ $scope.state = { status: "failed", message: "connection failure "+phase, code: response.status };
+ }
+ clearLocalCache();
+ }
+
+ this.logout = (expectAlreadyLoggedOut) => {
+ let useGet = $stateParams.useGet;
+ let keepCreds = $stateParams.keepCreds;
+
+ $scope.state = { status: "logging-out" };
+ let params = {};
+ let ourToken = 'logging-out-from-webapp'; // used to ensure the 401 is because we logged out
+ if (!keepCreds) params.unauthorize = ourToken;
+ //let httpCall = useGet ? $http.get('/v1/logout', { params }) : $http.post('/v1/logout', params);
+ //httpCall
+ $timeout(()=>
+ $http({ url: '/v1/logout', method: useGet ? "GET" : "POST", params })
+ .then(response => {
+ if ($scope.debug) $log.info("Logout response", response);
+ $scope.state = { status: "just-logged-out" };
+ clearLocalCache();
+
+ }, error => {
+ if (error.data && error.data.message == ourToken) {
+ if ($scope.debug) $log.info("Logout response 401 - ", error);
+ if (expectAlreadyLoggedOut) {
+ $scope.state = { status: "success-after-logout" };
+ } else {
+ $scope.state = { status: "just-logged-out" };
+ }
+ clearLocalCache();
+
+ } else {
+ handleError(expectAlreadyLoggedOut ? "confirming logout" : "logging out", error, expectAlreadyLoggedOut);
+ }
+ }), 500 /* delay 500ms so other requests finish loading */);
+ }
+
+ this.retry = () => this.logout();
+
+ this.prompt = () => {
+ $scope.state.status = "prompt";
+ }
+
+ this.confirm = () => {
+ clearLocalCache();
+ this.logout(true);
+ }
+
+ if ($scope.state.status == "do-logout") this.logout();
}
diff --git a/ui-modules/logout/app/views/main/main.template.html b/ui-modules/logout/app/views/main/main.template.html
index 752ac93..ba5ff77 100644
--- a/ui-modules/logout/app/views/main/main.template.html
+++ b/ui-modules/logout/app/views/main/main.template.html
@@ -18,9 +18,72 @@
-->
<div class="container-fluid">
<div class="row">
- <div class="col-md-12 text-center">
- <h2>Thank you for using <%= getBrandedText('product.name') %>, see you soon</h2>
- <p><a class="btn btn-lg btn-primary" href="/">Log back in</a></p>
+ <div class="col-md-12 text-center text-narrow" ng-switch="state.status">
+ <div ng-switch-when="prompt">
+ <h2>Are you sure you wish to logout?</h2>
+ <p>
+ <a class="btn btn-lg btn-primary" ng-click="vm.logout()">Yes</a>
+ <a class="btn btn-lg btn-outline" href="/">No, return to <%= getBrandedText('product.name') %></a>
+ </p>
+ <div br-server-status></div>
+ </div>
+
+ <div ng-switch-when="logging-out">
+ <h2>Logging out...</h2>
+ <p> </p>
+ <p><a class="btn btn-lg btn-primary" ng-click="vm.retry()">Retry</a></p>
+ </div>
+
+ <div ng-switch-when="just-logged-out">
+ <h2>Logout successful</h2>
+ <p>Thank you for using <%= getBrandedText('product.name') %>. See you soon!</p>
+ <p><i>Note that some browsers may cache credentials,
+ particularly if you have this app open in multiple tabs
+ so you might not be prompted on a subsequent log in.</i></p>
+ <p> </p>
+ <p><a class="btn btn-lg btn-primary" href="/{{ debug || salt ? '#!/?' : '' }}{{ debug ? '&debug=true' : ''}}{{ salt ? '&salt='+(salt+1) : ''}}">Log back in</a></p>
+ <p ng-if="debug"><a class="btn btn-lg btn-outline" ng-click="vm.confirm()">Confirm and clear</a></p>
+ </div>
+
+ <div ng-switch-when="already-logged-out">
+ <h2>Already logged out</h2>
+ <p>Thank you for using <%= getBrandedText('product.name') %>. See you soon!</p>
+ <p> </p>
+ <p><a class="btn btn-lg btn-primary" href="/{{ debug || salt ? '#!/?' : '' }}{{ debug ? '&debug=true' : ''}}{{ salt ? '&salt='+(salt+1) : ''}}">Log back in</a></p>
+ <p ng-if="debug"><a class="btn btn-lg btn-outline" ng-click="vm.confirm()">Confirm and clear</a></p>
+ </div>
+
+ <div ng-switch-when="logout-confirmed">
+ <h2>Logout confirmed</h2>
+ <p>Thank you for using <%= getBrandedText('product.name') %>. See you soon!</p>
+ <p>The application is no longer able to log in to the server.</p>
+ <p> </p>
+ <p><a class="btn btn-lg btn-primary" href="/{{ debug || salt ? '#!/?' : '' }}{{ debug ? '&debug=true' : ''}}{{ salt ? '&salt='+(salt+1) : ''}}">Log back in</a></p>
+ <p ng-if="debug"><a class="btn btn-lg btn-outline" ng-click="vm.confirm()">Confirm and clear again</a></p>
+ </div>
+ <div ng-switch-when="success-after-logout">
+ <h2>Logged out again</h2>
+ <p>Thank you for using <%= getBrandedText('product.name') %>. See you soon!</p>
+
+ <p>The app was able to log in successfully and logout again.</p>
+ <p>If you did not log again, this suggests the browser may be caching credentials or the server is unsecured.</p>
+ <p>The server has confirmed it has deleted the session.</p>
+ <p> </p>
+ <p><a class="btn btn-lg btn-primary" href="/{{ debug || salt ? '#!/?' : '' }}{{ debug ? '&debug=true' : ''}}{{ salt ? '&salt='+(salt+1) : ''}}">Log back in</a></p>
+ <p ng-if="debug"><a class="btn btn-lg btn-outline" ng-click="vm.confirm()">Confirm and clear</a></p>
+ </div>
+
+
+ <div ng-switch-default>
+ <h2>Logout failed<span ng-if="state.message">: {{ state.message }}</span></h2>
+ <p ng-if="state.code==403"><i>CSRF cookie errors are common in development or can indicate that multiple sessions are open.
+ Closing other tabs and retrying will usually fix this.</i></p>
+ <p><i>More information may be available in the logs.</i></p>
+ <p> </p>
+ <p><a class="btn btn-lg btn-primary" ng-click="vm.retry()">Retry</a></p>
+ <p ng-if="debug"><a class="btn btn-lg btn-outline" ui-sref="/prompt">Prompt to logout again</a></p>
+ <p><a class="btn btn-lg btn-outline" href="/">Return to <%= getBrandedText('product.name') %> (login if needed)</a></p>
+ </div>
</div>
</div>
</div>
diff --git a/ui-modules/logout/src/main/webapp/WEB-INF/web.xml b/ui-modules/logout/src/main/webapp/WEB-INF/web.xml
index 0355330..5955b71 100644
--- a/ui-modules/logout/src/main/webapp/WEB-INF/web.xml
+++ b/ui-modules/logout/src/main/webapp/WEB-INF/web.xml
@@ -28,6 +28,46 @@
<welcome-file>index.html</welcome-file>
</welcome-file-list>
- <!--no security for this module so we can confirm that the user has logged out-->
+<!-- NOT registered as a brooklyn ui module; don't want it to be listed as a module
+ <listener>
+ <listener-class>org.apache.brooklyn.ui.modularity.module.api.UiModuleListener</listener-class>
+ </listener>
+-->
+
+ <!--FILTERS :: START-->
+ <filter>
+ <filter-name>ui-module-filter</filter-name>
+ <filter-class>org.apache.brooklyn.ui.modularity.module.api.UiModuleFilter</filter-class>
+ <init-param>
+ <param-name>Cache-Control</param-name>
+ <param-value>public, max-age=604800</param-value><!--Cache static content for 1 week-->
+ </init-param>
+ </filter>
+ <filter>
+ <filter-name>brooklyn-security-filter</filter-name>
+ <filter-class>org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterJavax</filter-class>
+ </filter>
+ <filter>
+ <filter-name>GzipFilter</filter-name>
+ <filter-class>org.eclipse.jetty.servlets.GzipFilter</filter-class>
+ <init-param>
+ <param-name>mimeTypes</param-name>
+ <param-value>text/html,text/plain,text/xml,application/xhtml+xml,text/css,application/javascript,image/svg+xml</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>ui-module-filter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>brooklyn-security-filter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>GzipFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+ <!--FILTERS :: END-->
</web-app>