/* * RHQ Management Platform * Copyright (C) 2005-2012 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.coregui.client; import java.util.EnumSet; import java.util.List; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.http.client.URL; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.History; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.Location; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.core.KeyIdentifier; import com.smartgwt.client.i18n.SmartGwtMessages; import com.smartgwt.client.types.Overflow; import com.smartgwt.client.util.KeyCallback; import com.smartgwt.client.util.Page; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.layout.VLayout; import org.rhq.core.domain.cloud.Server.OperationMode; import org.rhq.core.domain.common.ProductInfo; import org.rhq.coregui.client.admin.AdministrationView; import org.rhq.coregui.client.alert.AlertHistoryView; import org.rhq.coregui.client.bundle.BundleTopView; import org.rhq.coregui.client.dashboard.DashboardsView; import org.rhq.coregui.client.gwt.GWTServiceLookup; import org.rhq.coregui.client.help.HelpView; import org.rhq.coregui.client.inventory.InventoryView; import org.rhq.coregui.client.inventory.groups.detail.ResourceGroupDetailView; import org.rhq.coregui.client.inventory.groups.detail.ResourceGroupTopView; import org.rhq.coregui.client.inventory.resource.detail.ResourceTopView; import org.rhq.coregui.client.menu.MenuBarView; import org.rhq.coregui.client.report.ReportTopView; import org.rhq.coregui.client.report.tag.TaggedView; import org.rhq.coregui.client.test.TestDataSourceResponseStatisticsView; import org.rhq.coregui.client.test.TestRemoteServiceStatisticsView; import org.rhq.coregui.client.test.TestTopView; import org.rhq.coregui.client.util.ErrorHandler; import org.rhq.coregui.client.util.Log; import org.rhq.coregui.client.util.message.Message; import org.rhq.coregui.client.util.message.Message.Option; import org.rhq.coregui.client.util.message.Message.Severity; import org.rhq.coregui.client.util.message.MessageCenter; /** * The GWT {@link EntryPoint entry point} to the RHQ GUI. * * @author Greg Hinkle * @author Ian Springer */ public class CoreGUI implements EntryPoint, ValueChangeHandler<String>, Event.NativePreviewHandler { public static final String CONTENT_CANVAS_ID = "BaseContent"; // This must come first to ensure proper I18N class loading for dev mode private static final Messages MSG = GWT.create(Messages.class); private static final MessageConstants MSGCONST = GWT.create(MessageConstants.class); private static final SmartGwtMessages SMART_GWT_MSG = GWT.create(SmartGwtMessages.class); private static final String DEFAULT_VIEW = DashboardsView.VIEW_ID.getName(); private static final String LOGOUT_VIEW = "LogOut"; private static String currentView; private static ViewPath currentViewPath; // just to avoid constructing this over and over. the ordering is important, more complex viewPaths first, // javascript will greedily match "Resource" and give up trying after that, missing "Resource/AutoGroup" for // example. private static final String TREE_NAV_VIEW_PATTERN = "(" // + ResourceGroupDetailView.AUTO_GROUP_VIEW + "|" // + ResourceGroupDetailView.AUTO_CLUSTER_VIEW // + ResourceGroupTopView.VIEW_ID + "|" // + ResourceTopView.VIEW_ID + "|" // + ")/[^/]*"; private static ErrorHandler errorHandler = new ErrorHandler(); private static MessageCenter messageCenter; private static CoreGUI coreGUI; // store a message to be posted in the message center during the next renderView processing. private static Message pendingMessage; // store the fact that we want the ViewPath for the next renderView to have refresh on. This allows us to // ask for a refresh even when changing viewPaths, which can be useful when we want to say, refresh a LHS tree // while also changing the main content. Typically we refresh only when the path is not changed. private static boolean pendingRefresh = false; //spinder [BZ 731864] defining variable that can be set at build time to enable/disable TAG ui components. // This will be set to 'false' on the release branch. private static boolean enableTagsForUI = Boolean.valueOf(MSG.enable_tags()); private static boolean rhq = true; public static boolean isRHQ() { return rhq; } public static boolean isTagsEnabledForUI() { return enableTagsForUI; } private RootCanvas rootCanvas; private MenuBarView menuBarView; private int rpcTimeout; private ProductInfo productInfo; @Override public void onModuleLoad() { String hostPageBaseURL = GWT.getHostPageBaseURL(); if (!hostPageBaseURL.contains("/coregui/")) { Log.info("Suppressing load of CoreGUI module"); return; // suppress loading this module if not using the new GWT app } rpcTimeout = -1; String rpcTimeoutParam = Location.getParameter("rpcTimeout"); if (rpcTimeoutParam != null) { try { rpcTimeout = Integer.parseInt(rpcTimeoutParam) * 1000; } catch (NumberFormatException ignored) { // nada } } coreGUI = this; Event.addNativePreviewHandler(this); registerPageKeys(); GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() { public void onUncaughtException(Throwable e) { if (e instanceof ViewChangedException) { ViewChangedException viewChangedException = (ViewChangedException) e; String obsoleteView = viewChangedException.getObsoleteView(); Log.debug("User navigated to view [" + currentView + "] before view [" + obsoleteView + "] was done rendering - rendering of view [" + obsoleteView + "] has been aborted."); } else { getErrorHandler().handleError(MSG.view_core_uncaught(), e); } } }); /* after having this enabled for a while, the majority opinion is that this is more annoying than helpful * Window.addWindowClosingHandler(new Window.ClosingHandler() { public void onWindowClosing(Window.ClosingEvent event) { event.setMessage("Are you sure you want to leave RHQ?"); } }); */ messageCenter = new MessageCenter(); UserSessionManager.login(); } public int getRpcTimeout() { return rpcTimeout; } @Override public void onPreviewNativeEvent(Event.NativePreviewEvent event) { if (SC.isIE() && event.getTypeInt() == Event.ONCLICK) { NativeEvent nativeEvent = event.getNativeEvent(); EventTarget target = nativeEvent.getEventTarget(); if (Element.is(target)) { Element element = Element.as(target); if ("a".equalsIgnoreCase(element.getTagName())) { // make sure it's not a hyperlink that GWT already handles if (element.getPropertyString("__listener") == null) { String url = element.getAttribute("href"); String viewPath = getViewPath(url); if (viewPath != null) { GWT.log("Forcing History.newItem(\"" + viewPath + "\")..."); History.newItem(viewPath); nativeEvent.preventDefault(); } } } } } } private static String getViewPath(String url) { String token; if (url.startsWith("#")) { token = url.substring(1); } else if (url.startsWith("/#")) { token = url.substring(2); } else if (url.contains(Location.getHost()) && url.indexOf('#') > 0) { token = url.substring(url.indexOf('#') + 1); } else { token = null; } return token; } private void registerPageKeys() { if (isDebugMode()) { KeyIdentifier debugKey = new KeyIdentifier(); debugKey.setCtrlKey(true); debugKey.setKeyName("D"); Page.registerKey(debugKey, new KeyCallback() { public void execute(String keyName) { SC.showConsole(); } }); } // Control-Shift-S for aggregate rpc service stats KeyIdentifier statisticsWindowKey = new KeyIdentifier(); statisticsWindowKey.setCtrlKey(true); statisticsWindowKey.setShiftKey(true); statisticsWindowKey.setAltKey(false); statisticsWindowKey.setKeyName("S"); Page.registerKey(statisticsWindowKey, new KeyCallback() { public void execute(String keyName) { TestRemoteServiceStatisticsView.showInWindow(); } }); // Control-Alt-S for response stats KeyIdentifier responseStatsWindowKey = new KeyIdentifier(); responseStatsWindowKey.setCtrlKey(true); statisticsWindowKey.setShiftKey(false); responseStatsWindowKey.setAltKey(true); responseStatsWindowKey.setKeyName("s"); Page.registerKey(responseStatsWindowKey, new KeyCallback() { public void execute(String keyName) { TestDataSourceResponseStatisticsView.showInWindow(); } }); KeyIdentifier messageCenterWindowKey = new KeyIdentifier(); messageCenterWindowKey.setCtrlKey(true); messageCenterWindowKey.setKeyName("M"); Page.registerKey(messageCenterWindowKey, new KeyCallback() { public void execute(String keyName) { menuBarView.getMessageCenter().showMessageCenterWindow(); } }); KeyIdentifier testTopViewKey = new KeyIdentifier(); testTopViewKey.setCtrlKey(true); testTopViewKey.setAltKey(true); testTopViewKey.setKeyName("t"); Page.registerKey(testTopViewKey, new KeyCallback() { public void execute(String keyName) { goToView("Test"); } }); } public static CoreGUI get() { return coreGUI; } public void init() { if (!LoginView.isLoginView()) { if (productInfo == null) { GWTServiceLookup.getSystemService().getProductInfo(new AsyncCallback<ProductInfo>() { @Override public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_aboutBox_failedToLoad(), caught); buildCoreUI(); } @Override public void onSuccess(ProductInfo result) { productInfo = result; rhq = (productInfo != null) && "RHQ".equals(productInfo.getShortName()); Window.setTitle(productInfo.getName()); buildCoreUI(); } }); } else { buildCoreUI(); } } } public ProductInfo getProductInfo() { return productInfo; } public void buildCoreUI() { // If the core gui is already built (eg. from previous login) skip the build and just refire event if (rootCanvas == null) { menuBarView = new MenuBarView(); menuBarView.setWidth("100%"); menuBarView.setExtraSpace(0); Canvas canvas = new Canvas(CONTENT_CANVAS_ID); canvas.setWidth100(); canvas.setHeight100(); canvas.setZIndex(0); rootCanvas = new RootCanvas(); rootCanvas.setOverflow(Overflow.HIDDEN); rootCanvas.addMember(menuBarView); rootCanvas.addMember(canvas); rootCanvas.draw(); History.addValueChangeHandler(this); } if (History.getToken().equals("") || History.getToken().equals(LOGOUT_VIEW)) { // init the rootCanvas to ensure a clean slate for a new user rootCanvas.initCanvas(); // go to default view if user doesn't specify a history token History.newItem(getDefaultView()); } else { // otherwise just fire an event for the bookmarked URL they are returning to and // ensure it is refreshed if it is a RefreshableView pendingRefresh = true; History.fireCurrentHistoryState(); } // The root canvas is hidden by user log out, show again if necessary if (!rootCanvas.isVisible()) { rootCanvas.show(); } GWTServiceLookup.getTopologyService().getCurrentServerOperationMode(new AsyncCallback<OperationMode>() { @Override public void onFailure(Throwable caught) { } @Override public void onSuccess(OperationMode result) { if (OperationMode.MAINTENANCE.equals(result)) { getMessageCenter().notify( new Message(MSG.server_maintanance_warning(), Severity.Warning, EnumSet.of(Option.Sticky))); } } }); } @Override public void onValueChange(ValueChangeEvent<String> stringValueChangeEvent) { currentView = URL.decodeQueryString(stringValueChangeEvent.getValue()); Log.debug("Handling history event for view: " + currentView); currentViewPath = new ViewPath(currentView); coreGUI.rootCanvas.renderView(currentViewPath); } public static native void showBusy(boolean on) /*-{ if ($wnd.loadQ == undefined) { $wnd.loadQ = []; } if (on) { $wnd.loadQ.push(on); $wnd.Pace.restart(); } else { $wnd.loadQ.shift(); if ($wnd.loadQ.length < 1) { $wnd.Pace.stop(); } } }-*/; public static void refresh() { showBusy(true); currentViewPath = new ViewPath(currentView, true); coreGUI.rootCanvas.renderView(currentViewPath); showBusy(false); } public Canvas createContent(String viewName) { Canvas canvas; boolean isLogout = LOGOUT_VIEW.equals(viewName); if (isLogout || LoginView.isLoginView()) { rootCanvas.hide(); LoginView logoutView = new LoginView(); canvas = logoutView; logoutView.showLoginDialog(isLogout); } else if (viewName.equals(DashboardsView.VIEW_ID.getName())) { canvas = new DashboardsView(); } else if (viewName.equals(InventoryView.VIEW_ID.getName())) { canvas = new InventoryView(); } else if (viewName.equals(ResourceTopView.VIEW_ID.getName())) { canvas = new ResourceTopView(); } else if (viewName.equals(ResourceGroupTopView.VIEW_ID.getName())) { canvas = new ResourceGroupTopView(); } else if (viewName.equals(ReportTopView.VIEW_ID.getName())) { canvas = new ReportTopView(); } else if (viewName.equals(BundleTopView.VIEW_ID.getName())) { canvas = new BundleTopView(); } else if (viewName.equals(AdministrationView.VIEW_ID.getName())) { canvas = new AdministrationView(); } else if (viewName.equals(HelpView.VIEW_ID.getName())) { canvas = new HelpView(); } else if (viewName.equals(TaggedView.VIEW_ID.getName())) { canvas = new TaggedView(); } else if (viewName.equals("Subsystems")) { canvas = new AlertHistoryView(); } else if (viewName.equals(TestTopView.VIEW_ID.getName())) { canvas = new TestTopView(); } else { canvas = null; } return canvas; } // -------------------- Static application utilities ---------------------- public static MessageCenter getMessageCenter() { return messageCenter; } public static ErrorHandler getErrorHandler() { return errorHandler; } private static String getDefaultView() { // TODO: should this be Dashboard or a User Preference? return DEFAULT_VIEW; } public static void goToView(String view) { goToView(view, null, false); } public static void goToView(String view, Message message) { goToView(view, message, false); } public static void goToView(String view, boolean refresh) { goToView(view, null, refresh); } public static void goToView(String view, Message message, boolean refresh) { pendingMessage = message; pendingRefresh = refresh; // if path starts with "#" (e.g. if caller used LinkManager to obtain some of the path), strip it off if (view.charAt(0) == '#') { view = view.substring(1); } String currentViewPath = History.getToken(); if (currentViewPath.equals(view)) { // We're already there - just refresh the view. refresh(); } else { if (view.matches(TREE_NAV_VIEW_PATTERN)) { // e.g. "Resource/10001" or "Resource/AutoGroup/10003" // TODO: need to support string IDs "Drift/History/0id_abcdefghijk" // TODO: see StringIDTableSection.ID_PREFIX // TODO: remember \D is a non-digit, and \d is a digit // TODO: String suffix = currentViewPath.replaceFirst("\\D*[^/]*", ""); // this might be OK if 0id_ starts with a digit // TODO: suffix = suffix.replaceFirst("((\\d.*)|(0id_[^/]*))", ""); if (!currentViewPath.startsWith(view)) { // The Node that was selected is not the same Node that was previously selected - it // may not even be the same node type. For example, the user could have moved from a // resource to an autogroup in the same tree. Try to keep the tab selection sticky as best as // possible while moving from one view to another by grabbing the end portion of the previous // history URL and append it to the new history URL. The suffix is assumed to follow the // ID (numeric) portion of the currentViewPath. String suffix = currentViewPath.replaceFirst("\\D*[^/]*", ""); // make sure we're not *too* sticky, stop no deeper than the subtab level. This prevents // trying to render non-applicable detail views. We'll do this by chopping at the start // of any other numeric in the path suffix = suffix.replaceFirst("\\d.*", ""); view += suffix; } } History.newItem(view); } } public static Messages getMessages() { return MSG; } public static MessageConstants getMessageConstants() { return MSGCONST; } public static SmartGwtMessages getSmartGwtMessages() { return SMART_GWT_MSG; } public void reset() { messageCenter.reset(); } private class RootCanvas extends VLayout implements BookmarkableView { private ViewId currentViewId; private Canvas currentCanvas; private RootCanvas() { setWidth100(); setHeight100(); } // TODO (ips, 09/06/11): i18n the title. private String getViewPathTitle(ViewPath viewPath) { // Set the default title to the view path minus any IDs. StringBuilder viewPathTitle = new StringBuilder(); String productName = (productInfo != null) ? productInfo.getName() : "RHQ"; List<ViewId> viewIds = viewPath.getViewPath(); if (!viewIds.isEmpty()) { viewPathTitle.append(productName).append(": "); viewPathTitle.append(viewIds.get(0)); for (int i = 1, viewPathSize = viewIds.size(); i < viewPathSize; i++) { ViewId viewId = viewIds.get(i); // None of our path elements start with a digit that is NOT an ID, so if we see an ID, skip it. if (!Character.isDigit(viewId.getPath().charAt(0))) { viewPathTitle.append(" | "); viewPathTitle.append(viewId.getPath()); } } } return viewPathTitle.toString(); } public void initCanvas() { // request a redraw of the MenuBarItem to ensure the correct session info is displayed getMember(0).markForRedraw(); // remove any current viewId so the next requested view generates new content this.currentViewId = null; } @Override public void renderView(final ViewPath viewPath) { // If the session is logged out ensure that we only navigate to the log out view, otherwise keep // our CoreGUI session alive by refreshing the session timer each time the user performs navigation if (UserSessionManager.isLoggedOut()) { if (!LOGOUT_VIEW.equals(viewPath.getCurrent().getPath())) { History.newItem(LOGOUT_VIEW); } return; } else { UserSessionManager.refresh(); } Window.setTitle(getViewPathTitle(viewPath)); // clear any message when navigating to a new view (not refreshing), the user is probably no longer interested if (!viewPath.isRefresh()) { coreGUI.menuBarView.getMessageBar().clearMessage(true); } if (viewPath.isEnd()) { // default view History.newItem(DEFAULT_VIEW); } else { if (pendingMessage != null) { getMessageCenter().notify(pendingMessage); pendingMessage = null; } if (pendingRefresh) { viewPath.setRefresh(true); pendingRefresh = false; } ViewId topLevelViewId = viewPath.getCurrent(); // e.g. Administration if (!topLevelViewId.equals(this.currentViewId)) { // Destroy the current canvas before creating the new one. This helps prevent locator // conflicts if the old and new content share (logical) widgets. A call to destroy (e.g. certain // IFrames/FullHTMLPane) can actually remove multiple children of the contentCanvas. As such, we // need to query for the children after each destroy to ensure only valid children are in the array. final Canvas contentCanvas = Canvas.getById(CONTENT_CANVAS_ID); Canvas[] children; while ((children = contentCanvas.getChildren()).length > 0) { children[0].destroy(); } // Set the new content and redraw this.currentViewId = topLevelViewId; // Using GWT Code Splitting feature to decrease the size of generated JS code using lazy // fetching. Each view built in createContent method has its Java Script code in a separate // file. final long startTime = System.currentTimeMillis(); GWT.runAsync(new RunAsyncCallback() { public void onFailure(Throwable caught) { Window.alert("Code download failed"); Log.error("Code download failed"); } public void onSuccess() { RootCanvas.this.currentCanvas = createContent(RootCanvas.this.currentViewId.getPath()); if (null != RootCanvas.this.currentCanvas) { contentCanvas.addChild(RootCanvas.this.currentCanvas); } contentCanvas.markForRedraw(); render(viewPath); Log.info("Time to Load first codesplit fragment: "+(System.currentTimeMillis() - startTime) + " ms."); } }); } else { render(viewPath); } } } private void render(ViewPath viewPath) { if (this.currentCanvas instanceof InitializableView) { final InitializableView initializableView = (InitializableView) this.currentCanvas; final ViewPath initializableViewPath = viewPath; new Timer() { final long startTime = System.currentTimeMillis(); @Override public void run() { if (initializableView.isInitialized()) { if (RootCanvas.this.currentCanvas instanceof BookmarkableView) { ((BookmarkableView) RootCanvas.this.currentCanvas).renderView(initializableViewPath .next()); } else { RootCanvas.this.currentCanvas.markForRedraw(); } } else { long elapsedMillis = System.currentTimeMillis() - startTime; if (elapsedMillis < 10000) { schedule(100); // Reschedule the timer. } } } }.run(); // fire the timer immediately } else { if (this.currentCanvas != null) { if (this.currentCanvas instanceof BookmarkableView) { ((BookmarkableView) this.currentCanvas).renderView(viewPath.next()); } else { this.currentCanvas.markForRedraw(); } } } } } public static boolean isDebugMode() { return !GWT.isScript(); } // Update property which records update/patch version: Ex. update-1, cp1, etc. public static String getUpdateVersion() { return ""; } }