/* * RHQ Management Platform * Copyright (C) 2005-2011 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.inventory.resource.discovery; import java.util.ArrayList; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.data.Criteria; import com.smartgwt.client.data.DataSource; import com.smartgwt.client.types.Autofit; import com.smartgwt.client.types.SelectionAppearance; import com.smartgwt.client.util.BooleanCallback; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.IButton; import com.smartgwt.client.widgets.events.ClickEvent; import com.smartgwt.client.widgets.events.ClickHandler; import com.smartgwt.client.widgets.form.DynamicForm; import com.smartgwt.client.widgets.form.fields.SelectItem; import com.smartgwt.client.widgets.form.fields.events.ChangedEvent; import com.smartgwt.client.widgets.form.fields.events.ChangedHandler; import com.smartgwt.client.widgets.grid.ListGridRecord; import com.smartgwt.client.widgets.grid.events.SelectionChangedHandler; import com.smartgwt.client.widgets.grid.events.SelectionEvent; import com.smartgwt.client.widgets.layout.LayoutSpacer; import com.smartgwt.client.widgets.toolbar.ToolStrip; import com.smartgwt.client.widgets.tree.TreeGrid; import com.smartgwt.client.widgets.tree.TreeGridField; import com.smartgwt.client.widgets.tree.TreeNode; import com.smartgwt.client.widgets.tree.events.DataArrivedEvent; import com.smartgwt.client.widgets.tree.events.DataArrivedHandler; import org.rhq.core.domain.resource.InventoryStatus; import org.rhq.core.domain.resource.ResourceCategory; import org.rhq.coregui.client.CoreGUI; import org.rhq.coregui.client.IconEnum; import org.rhq.coregui.client.ImageManager; import org.rhq.coregui.client.RefreshableView; import org.rhq.coregui.client.components.table.TimestampCellFormatter; import org.rhq.coregui.client.components.view.HasViewName; import org.rhq.coregui.client.components.view.ViewName; import org.rhq.coregui.client.gwt.GWTServiceLookup; import org.rhq.coregui.client.gwt.ResourceGWTServiceAsync; import org.rhq.coregui.client.util.TableUtility; import org.rhq.coregui.client.util.enhanced.EnhancedIButton; import org.rhq.coregui.client.util.enhanced.EnhancedIButton.ButtonColor; import org.rhq.coregui.client.util.enhanced.EnhancedVLayout; import org.rhq.coregui.client.util.message.Message; /** * @author Greg Hinkle */ public class ResourceAutodiscoveryView extends EnhancedVLayout implements RefreshableView, HasViewName { public static final ViewName VIEW_ID = new ViewName("AutodiscoveryQueue", MSG.view_inventory_adq(), IconEnum.DISCOVERY_QUEUE); // Specify 3m timeout to compensate for import taking a long time for a large number of Resources. // TODO (ips, 08/31/11): Remove this once import has been refactored to be partially asynchronous, i.e. where the // call to importResources() flips all the Resources to a new COMMITTING inventory status // and then kicks off a background job to do the actual work of committing (syncing to // Agents, etc.). ResourceGWTServiceAsync importResourceService = GWTServiceLookup.getResourceService(3 * 60 * 1000); private boolean simple; private TreeGrid treeGrid; private ToolStrip footer; private AutodiscoveryQueueDataSource dataSource; // This allows the selection handler to ignore selection changes initiated by us, as opposed to by the user. private boolean selectionChangedHandlerDisabled; private boolean loadAllPlatforms; private ResourceGWTServiceAsync resourceService = GWTServiceLookup.getResourceService(); public ResourceAutodiscoveryView() { this(false); } public ResourceAutodiscoveryView(boolean simple) { setWidth100(); setHeight100(); this.simple = simple; } @Override protected void onInit() { super.onInit(); treeGrid = new TreeGrid(); treeGrid.setHeight100(); dataSource = new AutodiscoveryQueueDataSource(treeGrid); treeGrid.setDataSource(dataSource); treeGrid.setAutoFetchData(true); treeGrid.setResizeFieldsInRealTime(true); treeGrid.setAutoFitData(Autofit.HORIZONTAL); treeGrid.setWrapCells(true); treeGrid.setFixedRecordHeights(false); treeGrid.setFolderIcon(ImageManager.getDiscoveryQueuePlatformIconBase()); treeGrid.setNodeIcon(ImageManager.getResourceIcon(ResourceCategory.SERVER)); final TreeGridField name, key, type, description, status, ctime; name = new TreeGridField("name"); key = new TreeGridField("resourceKey"); type = new TreeGridField("typeName"); description = new TreeGridField("description"); status = new TreeGridField("statusLabel"); ctime = new TreeGridField("ctime"); TimestampCellFormatter.prepareDateField(ctime); if (!simple) { treeGrid.setFields(name, key, type, description, status, ctime); } else { treeGrid.setFields(name, key, type, status); } treeGrid.setSelectionAppearance(SelectionAppearance.CHECKBOX); // Do this last since it causes the TreeGrid to be initialized. treeGrid.deselectAllRecords(); addMember(treeGrid); footer = new ToolStrip(); footer.setPadding(5); footer.setWidth100(); footer.setMembersMargin(15); addMember(footer); final IButton importButton = new EnhancedIButton(MSG.common_button_import(), ButtonColor.BLUE); final IButton ignoreButton = new EnhancedIButton(MSG.view_autoDiscoveryQ_ignore(), ButtonColor.RED); final IButton unignoreButton = new EnhancedIButton(MSG.view_autoDiscoveryQ_unignore()); footer.addMember(importButton); footer.addMember(ignoreButton); footer.addMember(unignoreButton); disableButtons(importButton, ignoreButton, unignoreButton); footer.addMember(new LayoutSpacer()); // The remaining footer items (status filter, (de)select all buttons, and refresh button) will be right-aligned. DynamicForm form = new DynamicForm(); final SelectItem statusSelectItem = new SelectItem("status", MSG.view_autoDiscoveryQ_showStatus()); statusSelectItem.setValueMap(AutodiscoveryQueueDataSource.NEW, AutodiscoveryQueueDataSource.IGNORED, AutodiscoveryQueueDataSource.NEW_AND_IGNORED); statusSelectItem.setValue(AutodiscoveryQueueDataSource.NEW); statusSelectItem.setWrapTitle(false); form.setItems(statusSelectItem); statusSelectItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent changedEvent) { treeGrid.fetchData(new Criteria("status", (String) statusSelectItem.getValue())); } }); footer.addMember(form); final IButton selectAllButton = new EnhancedIButton(MSG.view_autoDiscoveryQ_selectAll()); footer.addMember(selectAllButton); final IButton deselectAllButton = new EnhancedIButton(MSG.view_autoDiscoveryQ_deselectAll()); deselectAllButton.setDisabled(true); footer.addMember(deselectAllButton); IButton refreshButton = new EnhancedIButton(MSG.common_button_refresh()); refreshButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { refresh(); } }); footer.addMember(refreshButton); treeGrid.addSelectionChangedHandler(new SelectionChangedHandler() { public void onSelectionChanged(SelectionEvent selectionEvent) { if (selectionChangedHandlerDisabled || selectionEvent.isRightButtonDown()) { return; } selectionChangedHandlerDisabled = true; final TreeNode selectedNode = treeGrid.getTree() .findById(selectionEvent.getRecord().getAttribute("id")); TreeNode parentNode = treeGrid.getTree().getParent(selectedNode); boolean isPlatform = treeGrid.getTree().isRoot(parentNode); boolean isCheckboxMarked = treeGrid.isSelected(selectedNode); if (isPlatform) { if (isCheckboxMarked) { SC.ask(MSG.view_autoDiscoveryQ_confirmSelect(), new BooleanCallback() { public void execute(Boolean confirmed) { if (confirmed && !treeGrid.getTree().hasChildren(selectedNode)) { selectedNode.setAttribute("selectChildOnArrival", "true"); treeGrid.getTree().loadChildren(selectedNode); } else { if (confirmed) { selectAllPlatformChildren(selectedNode); } updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } } }); } else { selectedNode.setAttribute("autoSelectChildren", "false"); treeGrid.deselectRecords(treeGrid.getTree().getChildren(selectedNode)); // the immediate redraw below should not be necessary, but without it the deselected // platform checkbox remained checked. // treeGrid.redraw(); updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } } else { if (isCheckboxMarked) { if (!treeGrid.isSelected(parentNode)) { treeGrid.selectRecord(parentNode); } } updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } } }); treeGrid.addDataArrivedHandler(new DataArrivedHandler() { public void onDataArrived(DataArrivedEvent dataArrivedEvent) { if (treeGrid != null) { TreeNode parent = dataArrivedEvent.getParentNode(); if (!treeGrid.getTree().isRoot(parent)) { // This flag can be set when we select a platform or as part of selectAll button handling. It // means that we want to immediately select the child server nodes. boolean selectChildOnArrival = Boolean.valueOf(parent.getAttribute("selectChildOnArrival")); if (selectChildOnArrival) { // data includes the platform, just get the descendant servers treeGrid.selectRecords(treeGrid.getData().getDescendantLeaves()); } } // This logic is relevant to what we do in the selectAll button boolean endDisable = true; if (loadAllPlatforms) { TreeNode rootNode = treeGrid.getTree().getRoot(); TreeNode[] platformNodes = treeGrid.getTree().getChildren(rootNode); for (TreeNode platformNode : platformNodes) { if (!treeGrid.getTree().isLoaded(platformNode)) { endDisable = false; break; } } } if (endDisable) { updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } // NOTE: Due to a SmartGWT bug, the TreeGrid is not automatically redrawn upon data arrival, and // calling treeGrid.markForRedraw() doesn't redraw it either. The user can mouse over the grid // to cause it to redraw, but it is obviously not reasonable to expect that. So we must // explicitly call redraw() here. treeGrid.redraw(); } } }); dataSource.addFailedFetchListener(new AsyncCallback() { // just in case we have a failure while fetching, try to make sure we don't lock up the view. public void onFailure(Throwable caught) { loadAllPlatforms = false; updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } public void onSuccess(Object result) { // never called } }); selectAllButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { SC.ask(MSG.view_autoDiscoveryQ_confirmSelectAll(), new BooleanCallback() { public void execute(Boolean selectChildServers) { selectionChangedHandlerDisabled = true; TreeNode rootNode = treeGrid.getTree().getRoot(); TreeNode[] platformNodes = treeGrid.getTree().getChildren(rootNode); if (selectChildServers) { disableButtons(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); // we may need to fetch the child servers in order to select them. ArrayList<TreeNode> platformsToLoad = new ArrayList<TreeNode>(); for (TreeNode platformNode : platformNodes) { if (treeGrid.getTree().isLoaded(platformNode)) { // if loaded then just select the nodes if (!treeGrid.isSelected(platformNode)) { treeGrid.selectRecord(platformNode); } treeGrid.selectRecords(treeGrid.getTree().getChildren(platformNode)); } else { platformsToLoad.add(platformNode); } } if (platformsToLoad.isEmpty()) { // we're done, everything is already loaded treeGrid.markForRedraw(); updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } else { // this plays with the dataArrivedHandler which is responsible for // doing the child selection and checking to see if all platforms have been loaded // as the child data comes in. loadAllPlatforms = true; for (TreeNode platformToLoad : platformsToLoad) { // load and select if (!treeGrid.isSelected(platformToLoad)) { treeGrid.selectRecord(platformToLoad); } platformToLoad.setAttribute("selectChildOnArrival", "true"); treeGrid.getTree().loadChildren(platformToLoad); } } } else { treeGrid.selectRecords(platformNodes); treeGrid.markForRedraw(); updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } } }); } }); deselectAllButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { selectionChangedHandlerDisabled = true; treeGrid.deselectAllRecords(); treeGrid.markForRedraw(); updateButtonEnablement(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); selectionChangedHandlerDisabled = false; } }); importButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { disableButtons(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); // TODO (ips): Make the below message sticky, but add a new ClearSticky Message option that the // below callback methods can use to clear it once the importResources() call has // completed. CoreGUI.getMessageCenter().notify( new Message(MSG.view_autoDiscoveryQ_importInProgress(), Message.Severity.Info, EnumSet .of(Message.Option.Transient))); importResources(getSelectedPlatforms()); } }); ignoreButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { disableButtons(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); CoreGUI.getMessageCenter().notify( new Message(MSG.view_autoDiscoveryQ_ignoreInProgress(), Message.Severity.Info, EnumSet .of(Message.Option.Transient))); // assuming here that the ignore list will not be massive and that we can do it all in one go, // otherwise re-impl this like import. resourceService.ignoreResources(getAllSelectedIds(), new AsyncCallback<Void>() { public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_autoDiscoveryQ_ignoreFailure(), caught); } public void onSuccess(Void result) { CoreGUI.getMessageCenter().notify( new Message(MSG.view_autoDiscoveryQ_ignoreSuccessful(), Message.Severity.Info)); refresh(); } }); } }); unignoreButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { disableButtons(selectAllButton, deselectAllButton, importButton, ignoreButton, unignoreButton); CoreGUI.getMessageCenter().notify( new Message(MSG.view_autoDiscoveryQ_unignoreInProgress(), Message.Severity.Info, EnumSet .of(Message.Option.Transient))); // assuming here that the unignore list will not be massive and that we can do it all in one go, // otherwise re-impl this like import. resourceService.unignoreResources(getAllSelectedIds(), new AsyncCallback<Void>() { public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_autoDiscoveryQ_unignoreFailure(), caught); } public void onSuccess(Void result) { CoreGUI.getMessageCenter().notify( new Message(MSG.view_autoDiscoveryQ_unignoreSuccessful(), Message.Severity.Info)); refresh(); } }); } }); } // run through the platforms, committing ids for for 1 platform on each pass. doing it in chunks prevents // a timeout for a large import request. private void importResources(final List<TreeNode> platforms) { int[] idsToImport = null; for (Iterator<TreeNode> i = platforms.iterator(); i.hasNext();) { TreeNode platformNode = i.next(); int[] uncommittedIdsForPlatform = getPlatformSelectedIds(platformNode); // remove this platform, it either has nothing to import, or its resources will be imported i.remove(); if (uncommittedIdsForPlatform.length > 0) { idsToImport = uncommittedIdsForPlatform; break; } } if (null == idsToImport) { CoreGUI.getMessageCenter().notify( new Message(MSG.view_autoDiscoveryQ_importSuccessful(), Message.Severity.Info)); refresh(); return; } importResourceService.importResources(idsToImport, new AsyncCallback<Void>() { public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_autoDiscoveryQ_importFailure(), caught); refresh(); } public void onSuccess(Void result) { // recurse on the smaller platform list... importResources(platforms); } }); } private void selectAllPlatformChildren(TreeNode platformNode) { for (ListGridRecord child : treeGrid.getTree().getChildren(platformNode)) { if (!treeGrid.isSelected(child)) { treeGrid.selectRecord(child); } } } private void updateButtonEnablement(IButton selectAllButton, IButton deselectAllButton, IButton importButton, IButton ignoreButton, IButton unignoreButton) { if (treeGrid.getSelectedRecords().length == 0) { selectAllButton.setDisabled(treeGrid.getRecords().length == 0); deselectAllButton.setDisabled(true); importButton.setDisabled(true); ignoreButton.setDisabled(true); unignoreButton.setDisabled(true); markForRedraw(); return; } boolean allSelected = (treeGrid.getSelectedRecords().length == treeGrid.getRecords().length); selectAllButton.setDisabled(allSelected); deselectAllButton.setDisabled(false); boolean importOk = false; boolean ignoreOk = false; boolean unignoreOk = false; for (ListGridRecord listGridRecord : treeGrid.getSelectedRecords()) { TreeNode node = treeGrid.getTree().findById(listGridRecord.getAttribute("id")); String status = node.getAttributeAsString("status"); importOk |= InventoryStatus.NEW.name().equals(status); unignoreOk |= InventoryStatus.IGNORED.name().equals(status); ignoreOk |= InventoryStatus.NEW.name().equals(status); } importButton.setDisabled(!importOk || unignoreOk); ignoreButton.setDisabled(!ignoreOk || unignoreOk); unignoreButton.setDisabled(!unignoreOk || importOk || ignoreOk); markForRedraw(); } private void disableButtons(IButton... buttons) { for (IButton button : buttons) { button.setDisabled(true); } } private List<TreeNode> getSelectedPlatforms() { TreeNode rootNode = treeGrid.getTree().getRoot(); TreeNode[] platformNodes = treeGrid.getTree().getChildren(rootNode); List<TreeNode> result = new ArrayList<TreeNode>(platformNodes.length); for (TreeNode platformNode : platformNodes) { if (treeGrid.isSelected(platformNode)) { result.add(platformNode); } } return result; } private int[] getPlatformSelectedIds(TreeNode platformNode) { List<Integer> result = new ArrayList<Integer>(); if (!InventoryStatus.COMMITTED.name().equals(platformNode.getAttributeAsString("status"))) { result.add(Integer.parseInt(platformNode.getAttributeAsString("id"))); } for (ListGridRecord childNode : treeGrid.getTree().getChildren(platformNode)) { if (treeGrid.isSelected(childNode) && !InventoryStatus.COMMITTED.name().equals(childNode.getAttributeAsString("status"))) { result.add(Integer.parseInt(childNode.getAttributeAsString("id"))); } } return TableUtility.getIds(result); } private int[] getAllSelectedIds() { List<Integer> selected = new ArrayList<Integer>(); for (ListGridRecord node : treeGrid.getSelectedRecords()) { if (!InventoryStatus.COMMITTED.name().equals(node.getAttributeAsString("status"))) { selected.add(Integer.parseInt(node.getAttributeAsString("id"))); } } return TableUtility.getIds(selected); } /** * Custom refresh operation, as we cannot extend Table because we use a TreeGrid, not a ListGrid. */ public void refresh() { if (this.treeGrid != null) { this.treeGrid.invalidateCache(); this.treeGrid.markForRedraw(); } } public DataSource getDataSource() { return dataSource; } public TreeGrid getTreeGrid() { return treeGrid; } @Override public ViewName getViewName() { return VIEW_ID; } }