/*
* Copyright 2012 JBoss Inc
*
* Licensed 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.
*/
package org.overlord.rtgov.ui.client.local.pages;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import org.jboss.errai.ui.nav.client.local.Page;
import org.jboss.errai.ui.nav.client.local.PageHiding;
import org.jboss.errai.ui.nav.client.local.TransitionAnchor;
import org.jboss.errai.ui.shared.api.annotations.DataField;
import org.jboss.errai.ui.shared.api.annotations.EventHandler;
import org.jboss.errai.ui.shared.api.annotations.Templated;
import org.overlord.commons.gwt.client.local.widgets.HtmlSnippet;
import org.overlord.commons.gwt.client.local.widgets.Pager;
import org.overlord.rtgov.ui.client.local.ClientMessages;
import org.overlord.rtgov.ui.client.local.events.TableSortEvent;
import org.overlord.rtgov.ui.client.local.pages.situations.SituationFilters;
import org.overlord.rtgov.ui.client.local.pages.situations.SituationTable;
import org.overlord.rtgov.ui.client.local.pages.situations.SituationWatcherEvents;
import org.overlord.rtgov.ui.client.local.services.NotificationService;
import org.overlord.rtgov.ui.client.local.services.SituationsServiceCaller;
import org.overlord.rtgov.ui.client.local.services.rpc.IRpcServiceInvocationHandler;
import org.overlord.rtgov.ui.client.local.services.rpc.IRpcServiceInvocationHandler.RpcServiceInvocationHandlerAdapter;
import org.overlord.rtgov.ui.client.local.widgets.ToggleSwitch;
import org.overlord.rtgov.ui.client.local.widgets.common.SortableTemplatedWidgetTable.SortColumn;
import org.overlord.rtgov.ui.client.model.BatchRetryResult;
import org.overlord.rtgov.ui.client.model.NotificationBean;
import org.overlord.rtgov.ui.client.model.SituationEventBean;
import org.overlord.rtgov.ui.client.model.SituationResultSetBean;
import org.overlord.rtgov.ui.client.model.SituationSummaryBean;
import org.overlord.rtgov.ui.client.model.SituationsFilterBean;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import elemental.client.Browser;
import elemental.events.Event;
import elemental.events.EventListener;
import elemental.html.WebSocket;
/**
* The "Situations" page.
*
* @author eric.wittmann@redhat.com
*/
@Templated("/org/overlord/rtgov/ui/client/local/site/situations.html#page")
@Page(path="situations")
@Dependent
public class SituationsPage extends AbstractPage {
@Inject
protected ClientMessages i18n;
@Inject
protected SituationsServiceCaller situationsService;
@Inject
protected NotificationService notificationService;
// Breadcrumbs
@Inject @DataField("back-to-dashboard")
private TransitionAnchor<DashboardPage> toDashboardPage;
@Inject @DataField("to-services")
private TransitionAnchor<ServicesPage> toServicesPage;
@Inject @DataField("filter-sidebar")
protected SituationFilters filtersPanel;
@Inject
@DataField("toggleFilterSwitch")
protected ToggleSwitch toggleFilterSwitch;
@Inject
@DataField("retrySituations")
protected Button retrySituations;
@Inject
@DataField("exportSituations")
protected Button exportSituations;
@Inject
@DataField("deleteSituations")
protected Button deleteSituations;
private boolean applyActionToFilteredRowsOnly = true;
@Inject @DataField("btn-refresh")
protected Button refreshButton;
@Inject @DataField("situations-none")
protected HtmlSnippet noDataMessage;
@Inject @DataField("situations-searching")
protected HtmlSnippet searchInProgressMessage;
@Inject @DataField("situations-table")
protected SituationTable situationsTable;
@Inject @DataField("situations-pager")
protected Pager pager;
@DataField("situations-range")
protected SpanElement rangeSpan = Document.get().createSpanElement();
@DataField("situations-total")
protected SpanElement totalSpan = Document.get().createSpanElement();
@Inject @DataField("sitwatch-btn")
protected Anchor sitWatchButton;
@Inject @DataField("sitwatch-events")
protected SituationWatcherEvents sitWatchEvents;
private int currentPage = 1;
private int numEvents = 0;
private WebSocket webSocket;
/**
* Constructor.
*/
public SituationsPage() {
}
/**
* Initialize the websocket
*/
// RTGOV-632 Move initial websocket setup until after table initialized to avoid concurrent
// request for access code on server side (EAP issue)
protected void initWebSocket() {
GWT.log("Subscribing to websocket for Situation notifications"); //$NON-NLS-1$
elemental.html.Window window = Browser.getWindow();
webSocket = window.newWebSocket("ws://"+window.getLocation().getHost()+"/rtgov-ui/acmws"); //$NON-NLS-1$
webSocket.setOnopen(new EventListener() {
@Override
public void handleEvent(Event evt) {
webSocket.send("{\"register\":{\"collection\":\"Situations\"}}");
}
});
webSocket.setOnmessage(new EventListener() {
@Override
public void handleEvent(Event evt) {
Object data=((elemental.events.MessageEvent)evt).getData();
JSONObject notification=(JSONObject)JSONParser.parseStrict((String)data);
if (((JSONString)notification.get("type")).stringValue().equals("Insert")) {
SituationEventBean bean=new SituationEventBean();
JSONObject value=(JSONObject)notification.get("value");
bean.setSituationId(((JSONString)value.get("id")).stringValue());
bean.setSeverity(((JSONString)value.get("severity")).stringValue());
bean.setSubject(((JSONString)value.get("subject")).stringValue());
bean.setType(((JSONString)value.get("type")).stringValue());
bean.setTimestamp(new java.util.Date());
onNewSituation(bean);
}
}
});
}
/**
* Called when the user navigates away from the page.
*/
@PageHiding
public void onPageHiding() {
GWT.log("Closing websocket for Situation notifcations"); //$NON-NLS-1$
if (webSocket != null) {
webSocket.close();
}
}
/**
* Called when a new situation event is received from the server.
* @param sitEvent
*/
protected void onNewSituation(SituationEventBean sitEvent) {
GWT.log("Situation event: " + sitEvent.getType()); //$NON-NLS-1$
this.numEvents++;
this.sitWatchEvents.add(sitEvent);
String btnHtml = "<i class=\"icon-warning-sign\"></i> <span>(" + this.numEvents + ")</span>"; //$NON-NLS-1$ //$NON-NLS-2$
this.sitWatchButton.setHTML(btnHtml);
sitWatchButton.setVisible(true);
}
/**
* Event handler that fires when the user clicks the situation watcher button.
* @param event
*/
@EventHandler("sitwatch-btn")
public void onSitWatchClick(ClickEvent event) {
int p = this.sitWatchButton.getAbsoluteTop() + this.sitWatchButton.getOffsetHeight() + 10;
this.sitWatchEvents.getElement().getStyle().setTop(p, Unit.PX);
this.sitWatchEvents.setVisible(!this.sitWatchEvents.isVisible());
this.sitWatchButton.setFocus(false);
this.sitWatchEvents.getElement().focus();
}
/**
* Called after construction.
*/
@PostConstruct
protected void postConstruct() {
filtersPanel.addValueChangeHandler(new ValueChangeHandler<SituationsFilterBean>() {
@Override
public void onValueChange(ValueChangeEvent<SituationsFilterBean> event) {
doSearch();
}
});
pager.addValueChangeHandler(new ValueChangeHandler<Integer>() {
@Override
public void onValueChange(ValueChangeEvent<Integer> event) {
doSearch(event.getValue());
}
});
situationsTable.addTableSortHandler(new TableSortEvent.Handler() {
@Override
public void onTableSort(TableSortEvent event) {
doSearch(currentPage);
}
});
toggleFilterSwitch.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
applyActionToFilteredRowsOnly = Boolean.valueOf(event.getValue());
}
});
// Hide column 1 when in mobile mode.
situationsTable.setColumnClasses(1, "desktop-only"); //$NON-NLS-1$
this.rangeSpan.setInnerText("?"); //$NON-NLS-1$
this.totalSpan.setInnerText("?"); //$NON-NLS-1$
sitWatchButton.setVisible(false);
sitWatchEvents.setVisible(false);
}
/**
* Event handler that fires when the user clicks the refresh button.
* @param event
*/
@EventHandler("btn-refresh")
public void onRefreshClick(ClickEvent event) {
doSearch(currentPage);
}
/**
* Kick off a search at this point so that we show some data in the UI.
* @see org.overlord.dtgov.ui.client.local.pages.AbstractPage#onPageShowing()
*/
@Override
protected void onPageShowing() {
doSearch();
// Refresh the artifact filters
filtersPanel.refresh();
}
/**
* Search for artifacts based on the current filter settings and search text.
*/
protected void doSearch() {
doSearch(1);
}
/**
* Search for situations based on the current filter settings.
* @param page
*/
protected void doSearch(int page) {
onSearchStarting();
currentPage = page;
SortColumn currentSortColumn = this.situationsTable.getCurrentSortColumn();
situationsService.search(filtersPanel.getValue(), page, currentSortColumn.columnId,
currentSortColumn.ascending, new IRpcServiceInvocationHandler<SituationResultSetBean>() {
@Override
public void onReturn(SituationResultSetBean data) {
updateTable(data);
updatePager(data);
resetSituationWatcher();
// RTGOV-632 Move initial websocket setup until after table initialized to avoid concurrent
// request for access code on server side (EAP issue)
if (webSocket == null) {
initWebSocket();
}
}
@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("situations.error-loading"), error); //$NON-NLS-1$
noDataMessage.setVisible(true);
searchInProgressMessage.setVisible(false);
}
});
}
/**
* Called when a new search is kicked off.
*/
protected void onSearchStarting() {
this.pager.setVisible(false);
this.searchInProgressMessage.setVisible(true);
this.situationsTable.setVisible(false);
this.noDataMessage.setVisible(false);
this.rangeSpan.setInnerText("?"); //$NON-NLS-1$
this.totalSpan.setInnerText("?"); //$NON-NLS-1$
}
/**
* Updates the table of situations with the given data.
* @param data
*/
protected void updateTable(SituationResultSetBean data) {
this.situationsTable.clear();
this.searchInProgressMessage.setVisible(false);
if (data.getSituations().size() > 0) {
for (SituationSummaryBean summaryBean : data.getSituations()) {
this.situationsTable.addRow(summaryBean);
}
this.situationsTable.setVisible(true);
} else {
this.noDataMessage.setVisible(true);
}
}
/**
* Updates the pager with the given data.
* @param data
*/
protected void updatePager(SituationResultSetBean data) {
int numPages = ((int) (data.getTotalResults() / data.getItemsPerPage())) + (data.getTotalResults() % data.getItemsPerPage() == 0 ? 0 : 1);
int thisPage = (data.getStartIndex() / data.getItemsPerPage()) + 1;
this.pager.setNumPages(numPages);
this.pager.setPage(thisPage);
this.pager.setVisible(numPages > 1);
int startIndex = data.getStartIndex() + 1;
int endIndex = startIndex + data.getSituations().size() - 1;
String rangeText = "" + startIndex + "-" + endIndex; //$NON-NLS-1$ //$NON-NLS-2$
String totalText = String.valueOf(data.getTotalResults());
this.rangeSpan.setInnerText(rangeText);
this.totalSpan.setInnerText(totalText);
}
/**
* Resets the situation watcher widget.
*/
protected void resetSituationWatcher() {
this.numEvents = 0;
sitWatchButton.setVisible(false);
this.sitWatchEvents.setVisible(false);
this.sitWatchEvents.clear();
}
/**
* Event handler that fires when the user clicks the retry button.
*
* @param event
*/
@EventHandler("retrySituations")
public void onRetryClick(ClickEvent event) {
SituationsFilterBean situationsFilterBean = applyActionToFilteredRowsOnly ? filtersPanel.getValue()
: new SituationsFilterBean();
final NotificationBean notificationBean = notificationService.startProgressNotification(
i18n.format("situation-details.resubmit-message-title"), //$NON-NLS-1$
i18n.format("situation.batch-retry-message-msg")); //$NON-NLS-1$
situationsService.resubmit(situationsFilterBean,
new RpcServiceInvocationHandlerAdapter<BatchRetryResult>() {
@Override
public void doOnReturn(BatchRetryResult data) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("situation-details.message-resubmitted"), //$NON-NLS-1$
i18n.format(
"situation.batch-retry-result", data.getProcessedCount(), data.getIgnoredCount(), data.getFailedCount())); //$NON-NLS-1$
}
@Override
public void doOnError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("situation-details.resubmit-error"), //$NON-NLS-1$
error);
}
@Override
public void doOnComplete(RpcResult<BatchRetryResult> result) {
}
});
}
@EventHandler("exportSituations")
public void onExportClick(ClickEvent event) {
SituationsFilterBean situationsFilterBean = applyActionToFilteredRowsOnly ? filtersPanel.getValue()
: new SituationsFilterBean();
final NotificationBean notificationBean = notificationService.startProgressNotification(
i18n.format("situation.export-message-title"), //$NON-NLS-1$
i18n.format("situation.export-message-msg")); //$NON-NLS-1$
situationsService.filter(situationsFilterBean, new RpcServiceInvocationHandlerAdapter<String>() {
@Override
public void doOnReturn(String id) {
UrlBuilder urlBuilder = Window.Location.createUrlBuilder();
String exportLocation = urlBuilder.setPath("rtgov-ui/rest/situations/export")
.setParameter("id", id).buildString();
Window.open(exportLocation, "_blank", "");
}
@Override
public void doOnError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("situation.export-error"), //$NON-NLS-1$
error);
}
@Override
public void doOnComplete(
org.overlord.rtgov.ui.client.local.services.rpc.IRpcServiceInvocationHandler.RpcResult<String> result) {
}
});
}
@EventHandler("deleteSituations")
public void onDeleteClick(ClickEvent event) {
if (!Window.confirm("This will delete " + (applyActionToFilteredRowsOnly ? "the selected" : "ALL")
+ " situation's. Do you wish to proceed?")) {
return;
}
SituationsFilterBean situationsFilterBean = applyActionToFilteredRowsOnly ? filtersPanel.getValue()
: new SituationsFilterBean();
final NotificationBean notificationBean = notificationService.startProgressNotification(
i18n.format("situation.delete-message-title"), //$NON-NLS-1$
i18n.format("situation.delete-message-msg")); //$NON-NLS-1$
situationsService.delete(situationsFilterBean, new RpcServiceInvocationHandlerAdapter<String>() {
@Override
public void doOnReturn(String deleteCount) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("situation.message-deleted"), //$NON-NLS-1$
i18n.format("situation.delete-result", deleteCount)); //$NON-NLS-1$
}
@Override
public void doOnError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("situation.delete-error"), //$NON-NLS-1$
error);
}
@Override
public void doOnComplete(
org.overlord.rtgov.ui.client.local.services.rpc.IRpcServiceInvocationHandler.RpcResult<String> result) {
situationsTable.clear();
updatePager(new SituationResultSetBean());
}
});
}
}