feat(ui): initial notifications service (REST calls, notification counts, etc)
MODIFY
common/servlet/src/main/java/io/apiman/common/servlet/ApimanCorsFilter.java
common/servlet/src/main/java/io/apiman/common/servlet/ApimanCorsFilter.java
*/
package io.apiman.common.servlet;
-import javax.servlet.*;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import io.apiman.common.config.ConfigFactory;
+
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.apache.commons.configuration.Configuration;
-import io.apiman.common.config.ConfigFactory;
/**
* A simple CORS filter for apiman.
*
* @author eric.wittmann@redhat.com
*/
+@SuppressWarnings("nls")
public class ApimanCorsFilter implements Filter {
public static final Configuration config;
@@ -67,23 +75,23 @@ public class ApimanCorsFilter implements Filter {
HttpServletResponse httpResp = (HttpServletResponse) response;
if (isPreflightRequest(httpReq) && originIsAllowed(httpReq)) {
- httpResp.setHeader("Access-Control-Allow-Origin", httpReq.getHeader("Origin")); //$NON-NLS-1$ //$NON-NLS-2$
+ httpResp.setHeader("Access-Control-Allow-Origin", httpReq.getHeader("Origin"));
if(!allowedCorsOrigins.contains("*")){
- httpResp.setHeader("Vary", "Origin"); //$NON-NLS-1$ //$NON-NLS-2$
+ httpResp.setHeader("Vary", "Origin");
}
- httpResp.setHeader("Access-Control-Allow-Credentials", "true"); //$NON-NLS-1$ //$NON-NLS-2$
- httpResp.setHeader("Access-Control-Max-Age", "1800"); //$NON-NLS-1$ //$NON-NLS-2$
- httpResp.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,HEAD,DELETE"); //$NON-NLS-1$ //$NON-NLS-2$
- httpResp.setHeader("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization"); //$NON-NLS-1$ //$NON-NLS-2$
- httpResp.setHeader("Access-Control-Expose-Headers", "X-Apiman-Error"); //$NON-NLS-1$ //$NON-NLS-2$
+ httpResp.setHeader("Access-Control-Allow-Credentials", "true");
+ httpResp.setHeader("Access-Control-Max-Age", "1800");
+ httpResp.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,HEAD,DELETE");
+ httpResp.setHeader("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization");
+ httpResp.setHeader("Access-Control-Expose-Headers", "X-Apiman-Error,Total-Count,X-Total-Count");
} else {
if (hasOriginHeader(httpReq) && originIsAllowed(httpReq)) {
- httpResp.setHeader("Access-Control-Allow-Origin", httpReq.getHeader("Origin")); //$NON-NLS-1$ //$NON-NLS-2$
+ httpResp.setHeader("Access-Control-Allow-Origin", httpReq.getHeader("Origin"));
if(!allowedCorsOrigins.contains("*")){
- httpResp.setHeader("Vary", "Origin"); //$NON-NLS-1$ //$NON-NLS-2$
+ httpResp.setHeader("Vary", "Origin");
}
- httpResp.setHeader("Access-Control-Allow-Credentials", "true"); //$NON-NLS-1$ //$NON-NLS-2$
- httpResp.setHeader("Access-Control-Expose-Headers", "X-Apiman-Error"); //$NON-NLS-1$ //$NON-NLS-2$
+ httpResp.setHeader("Access-Control-Allow-Credentials", "true");
+ httpResp.setHeader("Access-Control-Expose-Headers", "X-Apiman-Error,Total-Count,X-Total-Count");
}
chain.doFilter(httpReq, httpResp);
}

MODIFY
common/util/src/main/java/io/apiman/common/util/JsonUtil.java
common/util/src/main/java/io/apiman/common/util/JsonUtil.java
@@ -28,7 +28,7 @@ import static com.fasterxml.jackson.core.json.JsonReadFeature.ALLOW_UNQUOTED_FIE
* @author Marc Savy {@literal <marc@blackparrotlabs.io>}
*/
public class JsonUtil {
- public JsonUtil() {
+ private JsonUtil() {
}
private static final ObjectMapper OM = JsonMapper

MODIFY
manager/api/core/src/main/java/io/apiman/manager/api/core/IClientValidator.java
manager/api/core/src/main/java/io/apiman/manager/api/core/IClientValidator.java
@@ -18,6 +18,7 @@ package io.apiman.manager.api.core;
import io.apiman.manager.api.beans.clients.ClientStatus;
import io.apiman.manager.api.beans.clients.ClientVersionBean;
import io.apiman.manager.api.beans.contracts.ContractBean;
+import io.apiman.manager.api.core.exceptions.StorageException;
import java.util.List;
@@ -70,4 +71,7 @@ public interface IClientValidator {
boolean isReady(ClientVersionBean client, boolean hasContracts) throws Exception;
ClientStatus determineStatus(ClientVersionBean bean, List<ContractBean> contracts);
+
+ ClientStatus determineStatus(ClientVersionBean bean) throws StorageException;
+
}

MODIFY
manager/api/core/src/main/java/io/apiman/manager/api/core/util/EntityValidator.java
manager/api/core/src/main/java/io/apiman/manager/api/core/util/EntityValidator.java
@@ -19,20 +19,27 @@ import io.apiman.manager.api.beans.apis.ApiStatus;
import io.apiman.manager.api.beans.apis.ApiVersionBean;
import io.apiman.manager.api.beans.apis.ApiVersionStatusBean;
import io.apiman.manager.api.beans.apis.StatusItemBean;
+import io.apiman.manager.api.beans.clients.ClientBean;
import io.apiman.manager.api.beans.clients.ClientStatus;
import io.apiman.manager.api.beans.clients.ClientVersionBean;
import io.apiman.manager.api.beans.contracts.ContractBean;
import io.apiman.manager.api.beans.contracts.ContractStatus;
+import io.apiman.manager.api.beans.orgs.OrganizationBean;
import io.apiman.manager.api.beans.summary.ContractSummaryBean;
import io.apiman.manager.api.beans.summary.PolicySummaryBean;
import io.apiman.manager.api.core.IApiValidator;
import io.apiman.manager.api.core.IClientValidator;
+import io.apiman.manager.api.core.IStorage;
import io.apiman.manager.api.core.IStorageQuery;
+import io.apiman.manager.api.core.exceptions.StorageException;
import io.apiman.manager.api.core.i18n.Messages;
+import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
+import com.google.common.collect.Lists;
+
/**
* TODO could rework this to return or set a status?
*
@@ -44,6 +51,8 @@ public class EntityValidator implements IApiValidator, IClientValidator {
@Inject
private IStorageQuery storageQuery;
+ @Inject
+ private IStorage storage;
/**
* Constructor.
@@ -66,13 +75,16 @@ public class EntityValidator implements IApiValidator, IClientValidator {
return contracts.stream().allMatch(c -> c.getStatus() == ContractStatus.Created);
}
- // /**
- // * @see io.apiman.manager.api.core.IClientValidator#isReady(io.apiman.manager.api.beans.clients.ClientVersionBean, boolean)
- // */
- // @Override
- // public boolean isReady(ClientVersionBean client, boolean hasContracts) throws Exception {
- // return hasContracts;
- // }
+ /**
+ * @see io.apiman.manager.api.core.IClientValidator#isReady(io.apiman.manager.api.beans.clients.ClientVersionBean, boolean)
+ */
+ @Override
+ public boolean isReady(ClientVersionBean client, boolean hasContracts) throws Exception {
+ if (!hasContracts) {
+ return false;
+ }
+ return isReady(client);
+ }
@Override
public ClientStatus determineStatus(ClientVersionBean cvb, List<ContractBean> contracts) {
@@ -86,6 +98,14 @@ public class EntityValidator implements IApiValidator, IClientValidator {
return ClientStatus.Ready;
}
+ @Override
+ public ClientStatus determineStatus(ClientVersionBean bean) throws StorageException {
+ OrganizationBean orgBean = bean.getClient().getOrganization();
+ ClientBean cb = bean.getClient();
+ ArrayList<ContractBean> contracts = Lists.newArrayList(storage.getAllContracts(orgBean.getId(), cb.getId(), bean.getVersion()));
+ return determineStatus(bean, contracts);
+ }
+
/**
* @see io.apiman.manager.api.core.IApiValidator#isReady(io.apiman.manager.api.beans.apis.ApiVersionBean)
*/

MODIFY
manager/ui/war/plugins/api-manager/html/about.html
manager/ui/war/plugins/api-manager/html/about.html
<div ng-controller="Apiman.AboutController" class="page container-pf-nav-pf-vertical container-fluid apiman-about-page" data-field="page" ng-cloak="" ng-show="pageState == 'loaded'">
<div class="row">
<div class="col-md-12">
- <h2 apiman-i18n-key="about.page-heading">About apiman</h2>
+ <h2 apiman-i18n-key="about.page-heading">About Apiman</h2>
<hr />
</div>
<div class="col-md-3">

MODIFY
manager/ui/war/plugins/api-manager/html/dash.html
manager/ui/war/plugins/api-manager/html/dash.html
</div>
<div class="col-md-12 no-phone">
<span class="description" apiman-i18n-key="dash.page-description">
- Welcome to apiman, open source API management. Use this software to provide
+ Welcome to Apiman, open source API management. Use this software to provide
various APIs (APIs) to your consumers (Client Apps) through a secure, scalable,
and governed Gateway layer. API Management allows API Developers to centralize
control and analysis of their APIs. It also provides a central location for
<div class="dash-group">
<div class="title">
<i class="fa fa-code-fork"></i>
- <span apiman-i18n-key="notifications">Developer Portal</span>
+ <span apiman-i18n-key="notifications">Notifications</span>
</div>
<div class="description" apiman-i18n-key="dash.devportal-description">
- Manage your developer portal, including approving requests by developers to sign up to APIs
+ Manage your notifications, including approving requests by developers to sign up to APIs.
</div>
<div class="actions">
<ul>
<!-- <li>Developer account approvals</li>-->
- <li>API Signup Approval Requests</li>
+ <li><a href="">API Signup Approval Requests</a></li>
<!-- <li>Manage notifications -- probably should be in notifications area</li>-->
</ul>
</div>

MODIFY
manager/ui/war/plugins/api-manager/html/headers/community.include
manager/ui/war/plugins/api-manager/html/headers/community.include
</div>
<nav class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right navbar-iconic navbar-utility">
+ <li class="drawer-pf-trigger dropdown">
+ <!-- Consider adding dropdown drawer -->
+ <button class="btn btn-link nav-item-iconic drawer-pf-trigger-icon">
+ <span class="fa fa-bell dropdown-title" title="Notifications"></span>
+ <span class="badge badge-pf-bordered" ng-show="userNotificationCount > 0">{{ userNotificationCount }}</span><!-- in order to show the empty badge this requires a space, otherwise add a value -->
+ </button>
+ </li>
<li class="dropdown">
<a href="#0" class="dropdown-toggle nav-item-iconic" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span title="Username" class="fa fa-user"></span>&nbsp;{{ username }}
</li>
<li class="divider"></li>
<li>
- <a id="navbar-about" href="{{ pluginName }}/about" apiman-i18n-key="about-apiman">About apiman</a>
+ <a id="navbar-about" href="{{ pluginName }}/about" apiman-i18n-key="about-apiman">About Apiman</a>
</li>
<li class="divider"></li>
<li>

ADD
manager/ui/war/plugins/api-manager/html/notifications/notifications-dash.html
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+<body>
+
+</body>
+</html>
\ No newline at end of file

MODIFY
manager/ui/war/plugins/api-manager/ts/navbar.ts
manager/ui/war/plugins/api-manager/ts/navbar.ts
module Apiman {
export var NavbarController = _module.controller("Apiman.NavbarController",
- ['$scope', '$rootScope', 'Logger', 'Configuration', ($scope, $rootScope, Logger, Configuration) => {
+ ['$scope', '$rootScope', 'Logger', 'Configuration', 'NotificationService', ($scope, $rootScope, Logger, Configuration, NotificationService) => {
Logger.log("Current user is {0}.", Configuration.user.username);
$scope.username = Configuration.user.username;
$scope.logoutUrl = Configuration.apiman.logoutUrl;
@@ -10,6 +10,21 @@ module Apiman {
Logger.info('Returning to parent UI: {0}', Configuration.ui.backToConsole);
window.location.href = Configuration.ui.backToConsole;
};
+ $scope.userNotificationCount = null;
+
+ NotificationService.getNotificationCount(Configuration.user.username).then(
+ (count) => {
+ $scope.userNotificationCount = count;
+ Logger.info("Unread notifications = {0}", count);
+ },
+ (failure) => {
+ if (failure == null) {
+ Logger.error("Probably could not access Total-Count header due to CORS configuration issues when accessing Server API.")
+ } else {
+ Logger.error("Problem retrieving notifications count {0}", failure);
+ }
+ } // TODO do something useful
+ );
angular.element(document).ready(function () {
// Make header not scrollable

ADD
manager/ui/war/plugins/api-manager/ts/notifications.ts
manager/ui/war/plugins/api-manager/ts/notifications.ts
MODIFY
manager/ui/war/plugins/api-manager/ts/rpc.ts
manager/ui/war/plugins/api-manager/ts/rpc.ts
@@ -115,11 +115,30 @@ module ApimanRPC {
headers: {"Content-Type": undefined }, // Otherwise Angular will set the content type as JSON
url: `${Configuration.api.endpoint}/blobs`,
data: formData
- })
+ }).then(
+ (success) => $q.resolve(success.data),
+ (failure) => $q.reject(failure)
+ )
}
}
}]);
+ export var NotificationService = _module.factory('NotificationService', ['$http', 'Configuration', '$q',
+ ($http, Configuration, $q) => {
+ return {
+ getNotificationCount: (userId: string): Promise<number> => {
+ return $http({
+ method: 'HEAD',
+ url: `${Configuration.api.endpoint}/users/${userId}/notifications`
+ }).then(
+ success => $q.resolve(success.headers('Total-Count')),
+ failure => $q.reject(failure)
+ )
+ }
+ }
+ }
+ ]);
+
export var OrganizationSvcs = _module.factory('OrgSvcs', ['$resource', 'Configuration',
function($resource, Configuration) {
const endpoint = Configuration.api.endpoint + '/organizations/:organizationId/:entityType/:entityId/:versionsOrActivity/:version/:policiesOrActivity/:policyId/:policyChain';

feat(ui): initial notifications service (REST calls, notification counts, etc)