/* * 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()); } }); } }