/*
* RHQ Management Platform
* Copyright (C) 2005-2013 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.admin.storage;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_ADDRESS;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_ALERTS;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_AVAILABILITY;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_CQL_PORT;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_CTIME;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_MTIME;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_OPERATION_MODE;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_STATUS;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.types.VisibilityMode;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.HTMLFlow;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.FormItemIcon;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.form.fields.events.ClickEvent;
import com.smartgwt.client.widgets.form.fields.events.ClickHandler;
import com.smartgwt.client.widgets.form.fields.events.IconClickEvent;
import com.smartgwt.client.widgets.form.fields.events.IconClickHandler;
import com.smartgwt.client.widgets.layout.LayoutSpacer;
import com.smartgwt.client.widgets.layout.SectionStack;
import com.smartgwt.client.widgets.layout.SectionStackSection;
import org.rhq.core.domain.cloud.StorageNode;
import org.rhq.core.domain.cloud.StorageNode.OperationMode;
import org.rhq.core.domain.cloud.StorageNodeConfigurationComposite;
import org.rhq.core.domain.criteria.StorageNodeCriteria;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.ResourceAvailability;
import org.rhq.core.domain.measurement.composite.MeasurementDataNumericHighLowComposite;
import org.rhq.core.domain.operation.ResourceOperationHistory;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.collection.ArrayUtils;
import org.rhq.coregui.client.BookmarkableView;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.ImageManager;
import org.rhq.coregui.client.LinkManager;
import org.rhq.coregui.client.ViewPath;
import org.rhq.coregui.client.components.table.TimestampCellFormatter;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.util.Log;
import org.rhq.coregui.client.util.MeasurementUtility;
import org.rhq.coregui.client.util.StringUtility;
import org.rhq.coregui.client.util.enhanced.EnhancedHLayout;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
import org.rhq.coregui.client.util.message.Message;
/**
* Shows details of a storage node.
*
* @author Jirka Kremser
*/
public class StorageNodeDetailView extends EnhancedVLayout implements BookmarkableView {
private final int storageNodeId;
private static final int SECTION_COUNT = 3;
private final SectionStack sectionStack;
private EnhancedVLayout detailsLayout;
private EnhancedHLayout detailsAndLoadLayout;
private EnhancedVLayout loadLayout;
private SectionStackSection configurationSection;
private SectionStackSection detailsAndLoadSection;
private StaticTextItem alertsItem;
private HTMLFlow header;
private boolean alerts = false;
private StaticTextItem jmxPortItem;
private Integer jmxPort = null;
private volatile int initSectionCount = 0;
private int unackAlerts = -1;
public StorageNodeDetailView(int storageNodeId, HTMLFlow header) {
super();
this.storageNodeId = storageNodeId;
this.header = header;
header.setStyleName("HeaderLabel");
setHeight100();
setWidth100();
setOverflow(Overflow.AUTO);
sectionStack = new SectionStack();
sectionStack.setVisibilityMode(VisibilityMode.MULTIPLE);
sectionStack.setWidth100();
sectionStack.setHeight100();
sectionStack.setCanResizeSections(false);
}
@Override
protected void onInit() {
super.onInit();
if (alerts) {
return;
}
StorageNodeCriteria criteria = new StorageNodeCriteria();
criteria.addFilterId(storageNodeId);
criteria.fetchResource(true);
GWTServiceLookup.getStorageService().findStorageNodesByCriteria(criteria,
new AsyncCallback<PageList<StorageNode>>() {
public void onSuccess(final PageList<StorageNode> storageNodes) {
if (storageNodes == null || storageNodes.isEmpty() || storageNodes.size() != 1) {
onFailure(new Exception("No storage nodes have been found."));
}
final StorageNode node = storageNodes.get(0);
header.setContents(MSG.view_adminTopology_storageNodes_node() + " (" + node.getAddress() + ")");
prepareDetailsSection(node);
fetchStorageNodeConfigurationComposite(node);
fetchSparkLineDataForLoadComponent(node);
fetchUnackAlerts(storageNodeId, node.getResource() != null);
}
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(
MSG.view_adminTopology_message_fetchServerFail(String.valueOf(storageNodeId)) + " "
+ caught.getMessage(), caught);
initSectionCount = SECTION_COUNT;
}
});
}
private void fetchStorageNodeConfigurationComposite(final StorageNode node) {
if (node.getResource() == null) { // no associated resource yet
LayoutSpacer spacer = new LayoutSpacer();
spacer.setHeight(15);
HTMLFlow info = new HTMLFlow(MSG.view_adminTopology_storageNodes_detail_noConfiguration(node.getAddress()));
SectionStackSection section = new SectionStackSection(
MSG.view_adminTopology_storageNodes_detail_configuration());
section.setItems(spacer, info);
section.setExpanded(true);
section.setCanCollapse(false);
configurationSection = section;
initSectionCount++;
} else {
GWTServiceLookup.getStorageService().retrieveConfiguration(node,
new AsyncCallback<StorageNodeConfigurationComposite>() {
@Override
public void onFailure(Throwable caught) {
Message message = new Message(MSG.view_configurationHistoryDetails_error_loadFailure(),
Message.Severity.Warning);
initSectionCount = SECTION_COUNT;
CoreGUI.getMessageCenter().notify(message);
}
@Override
public void onSuccess(StorageNodeConfigurationComposite result) {
jmxPort = result.getJmxPort();
jmxPortItem.setValue(jmxPort);
prepareResourceConfigEditor(result);
}
});
}
}
private void fetchSparkLineDataForLoadComponent(final StorageNode storageNode) {
if (storageNode.getResource() == null) {
HTMLFlow info = new HTMLFlow(MSG.view_adminTopology_storageNodes_detail_noLoadData());
info.setExtraSpace(5);
loadLayout = new EnhancedVLayout();
loadLayout.setWidth100();
LayoutSpacer spacer = new LayoutSpacer();
spacer.setHeight(10);
HTMLFlow loadLabel = new HTMLFlow(MSG.view_adminTopology_storageNodes_detail_status());
loadLabel.addStyleName("formTitle");
loadLabel.setHoverWidth(300);
loadLayout.setMembers(spacer, loadLabel, info);
if (detailsAndLoadLayout == null) {
detailsAndLoadLayout = new EnhancedHLayout();
}
initSectionCount++;
} else {
GWTServiceLookup.getStorageService().findStorageNodeLoadDataForLast(storageNode, 8,
MeasurementUtility.UNIT_HOURS, 60,
new AsyncCallback<Map<String, List<MeasurementDataNumericHighLowComposite>>>() {
@Override
public void onFailure(Throwable caught) {
Message message = new Message(MSG.view_adminTopology_storageNodes_detail_loadDataFetchFail(),
Message.Severity.Warning);
initSectionCount = SECTION_COUNT;
CoreGUI.getMessageCenter().notify(message);
}
@Override
public void onSuccess(Map<String, List<MeasurementDataNumericHighLowComposite>> result) {
prepareLoadSection(sectionStack, storageNode, result);
}
});
}
}
private void fetchUnackAlerts(final int storageNodeId, final boolean isResourceIdSet) {
GWTServiceLookup.getStorageService().findNotAcknowledgedStorageNodeAlertsCounts(Arrays.asList(storageNodeId),
new AsyncCallback<List<Integer>>() {
@Override
public void onFailure(Throwable caught) {
Message message = new Message(
MSG.view_inventory_resource_loadFailed(String.valueOf(storageNodeId)), Message.Severity.Warning);
CoreGUI.getMessageCenter().notify(message);
initSectionCount = SECTION_COUNT;
}
@Override
public void onSuccess(List<Integer> result) {
if (result.isEmpty()) {
onFailure(new Exception(MSG.view_inventory_resource_loadFailed(String.valueOf(storageNodeId))));
} else {
unackAlerts = result.get(0);
if (alertsItem != null) {
alertsItem.setValue(isResourceIdSet ? StorageNodeAdminView.getAlertsString(
MSG.view_adminTopology_storageNodes_unackAlerts(), storageNodeId, unackAlerts) : MSG
.view_adminTopology_storageNodes_unackAlerts() + " (0)");
}
}
}
});
}
public boolean isInitialized() {
return initSectionCount >= SECTION_COUNT;
}
@Override
protected void onDraw() {
super.onDraw();
if (alerts) {
return;
}
// wait until we have all of the sections before we show them. We don't use InitializableView because,
// it seems they are not supported (in the applicable renderView()) at this level.
new Timer() {
final long startTime = System.currentTimeMillis();
public void run() {
if (isInitialized()) {
if (null != detailsAndLoadLayout) {
LayoutSpacer spacer = new LayoutSpacer();
spacer.setWidth("2%");
detailsAndLoadLayout.setMembers(detailsLayout, spacer, loadLayout);
detailsAndLoadLayout.setHeight(220);
detailsAndLoadSection = new SectionStackSection(
MSG.view_adminTopology_storageNodes_detail_info());
detailsAndLoadSection.setExpanded(true);
detailsAndLoadSection.setItems(detailsAndLoadLayout);
sectionStack.addSection(detailsAndLoadSection);
}
if (null != configurationSection) {
sectionStack.addSection(configurationSection);
}
addMember(sectionStack);
markForRedraw();
} else {
// don't wait forever, give up after 20s and show what we have
long elapsedMillis = System.currentTimeMillis() - startTime;
if (elapsedMillis > 20000) {
initSectionCount = SECTION_COUNT;
}
schedule(100); // Reschedule the timer.
}
}
}.run(); // fire the timer immediately
}
private void prepareDetailsSection(final StorageNode storageNode) {
detailsLayout = new EnhancedVLayout();
detailsLayout.setWidth("35%");
detailsLayout.addMember(buildDetailsForm(storageNode));
if (detailsAndLoadLayout == null) {
detailsAndLoadLayout = new EnhancedHLayout(0);
}
initSectionCount++;
}
private DynamicForm buildDetailsForm(final StorageNode storageNode) {
final DynamicForm form = new DynamicForm();
form.setMargin(10);
form.setWidth100();
form.setWrapItemTitles(false);
form.setNumCols(2);
final StaticTextItem nameItem = new StaticTextItem(FIELD_ADDRESS.propertyName(), FIELD_ADDRESS.title());
nameItem.setValue("<b>" + storageNode.getAddress() + "</b>");
final StaticTextItem cqlPortItem = new StaticTextItem(FIELD_CQL_PORT.propertyName(), FIELD_CQL_PORT.title());
cqlPortItem.setValue(storageNode.getCqlPort());
jmxPortItem = new StaticTextItem("jmxPort", MSG.view_adminTopology_storageNodes_settings_jmxPortName());
final StaticTextItem operationModeItem = new StaticTextItem(FIELD_OPERATION_MODE.propertyName(),
FIELD_OPERATION_MODE.title());
operationModeItem.setValue(storageNode.getOperationMode());
final StaticTextItem clusterStatusItem = new StaticTextItem(FIELD_STATUS.propertyName(), FIELD_STATUS.title());
clusterStatusItem.setValue(storageNode.getStatus().toString());
final StaticTextItem availabilityItem = new StaticTextItem(FIELD_AVAILABILITY.propertyName(),
FIELD_AVAILABILITY.title());
// make clickable link to associated resource
StaticTextItem resourceItem = new StaticTextItem("associatedResource",
MSG.view_adminTopology_storageNodes_detail_associatedResource());
String storageNodeItemText = "";
String availabilityItemText = imgHTML(ImageManager.getAvailabilityIconFromAvailType(AvailabilityType.UNKNOWN));
Resource storageNodeResource = storageNode.getResource();
if (storageNodeResource != null && storageNodeResource.getName() != null) {
String detailsUrl = LinkManager.getResourceLink(storageNodeResource.getId());
String formattedValue = StringUtility.escapeHtml(storageNodeResource.getName());
storageNodeItemText = LinkManager.getHref(detailsUrl, formattedValue);
// set the availability
ResourceAvailability availability = storageNodeResource.getCurrentAvailability();
if (storageNodeResource.getCurrentAvailability() != null
&& storageNodeResource.getCurrentAvailability().getAvailabilityType() != null) {
availabilityItemText = imgHTML(ImageManager.getAvailabilityIconFromAvailType(availability
.getAvailabilityType()));
}
} else {
storageNodeItemText = MSG.common_label_none();
}
resourceItem.setValue(storageNodeItemText);
availabilityItem.setValue(availabilityItemText);
StaticTextItem installationDateItem = new StaticTextItem(FIELD_CTIME.propertyName(), FIELD_CTIME.title());
installationDateItem.setValue(TimestampCellFormatter.format(Long.valueOf(storageNode.getCtime()),
TimestampCellFormatter.DATE_TIME_FORMAT_LONG));
StaticTextItem lastUpdateItem = new StaticTextItem(FIELD_MTIME.propertyName(), FIELD_MTIME.title());
lastUpdateItem.setValue(TimestampCellFormatter.format(Long.valueOf(storageNode.getMtime()),
TimestampCellFormatter.DATE_TIME_FORMAT_LONG));
alertsItem = new StaticTextItem(FIELD_ALERTS.propertyName(), FIELD_ALERTS.title());
alertsItem.setPrompt(MSG.view_adminTopology_storageNodes_detail_unackAlertsHover());
if (unackAlerts != -1) {
alertsItem.setValue(StorageNodeAdminView.getAlertsString(MSG.view_adminTopology_storageNodes_unackAlerts(),
storageNodeId, unackAlerts));
}
StaticTextItem messageItem = new StaticTextItem("message", MSG.view_adminTopology_storageNodes_detail_note());
StringBuffer message = new StringBuffer();
boolean isOk = true;
OperationMode operationMode = storageNode.getOperationMode();
boolean joining = operationMode == OperationMode.ANNOUNCE || operationMode == OperationMode.BOOTSTRAP
|| operationMode == OperationMode.ADD_MAINTENANCE;
boolean leaving = operationMode == OperationMode.DECOMMISSION || operationMode == OperationMode.UNANNOUNCE
|| operationMode == OperationMode.REMOVE_MAINTENANCE || operationMode == OperationMode.UNINSTALL;
if (storageNode.getResource() == null) {
message.append(MSG.view_adminTopology_storageNodes_detail_noResource() + "<br />");
isOk = false;
}
if (storageNode.getErrorMessage() != null) {
String noteTextPrefix = (joining ? MSG.view_adminTopology_storageNodes_detail_errorDeployment() + ": "
: (leaving ? MSG.view_adminTopology_storageNodes_detail_errorUndeployment() + ": " : ""));
message.append(noteTextPrefix);
message.append(storageNode.getErrorMessage()).append("<br />");
isOk = false;
} else if (storageNode.getFailedOperation() != null) {
message.append(MSG.view_adminTopology_storageNodes_detail_errorLastOperationFailed());
isOk = false;
}
if (isOk) {
message.append(MSG.view_adminTopology_storageNodes_detail_ok());
}
messageItem.setValue(message.toString());
StaticTextItem lastOperation = null;
StaticTextItem lastOperationAck = null;
boolean isOperationFailed = storageNode.getFailedOperation() != null
&& storageNode.getFailedOperation().getResource() != null;
if (isOperationFailed) {
ResourceOperationHistory operationHistory = storageNode.getFailedOperation();
String value = LinkManager.getSubsystemResourceOperationHistoryLink(operationHistory.getResource().getId(),
operationHistory.getId());
lastOperation = new StaticTextItem("lastOp", MSG.view_operationHistoryDetails_operation());
String operationTextPrefix = (joining ? MSG.view_adminTopology_storageNodes_detail_errorFailedDeployOp()
+ ": " : (leaving ? MSG.view_adminTopology_storageNodes_detail_errorFailedUneployOp() + ": " : ""));
lastOperation.setValue(operationTextPrefix
+ LinkManager.getHref(value, operationHistory.getOperationDefinition().getDisplayName()));
}
List<FormItem> formItems = new ArrayList<FormItem>(6);
formItems.addAll(Arrays.asList(nameItem, resourceItem, availabilityItem, cqlPortItem, jmxPortItem));
if (!CoreGUI.isDebugMode()) {
formItems.add(operationModeItem); // debug mode fails if this item is added
}
formItems.addAll(Arrays
.asList(clusterStatusItem, installationDateItem, lastUpdateItem, alertsItem, messageItem));
if (isOperationFailed) {
formItems.add(lastOperation);
}
if (null != storageNode.getErrorMessage() || null != storageNode.getFailedOperation()) {
lastOperationAck = new StaticTextItem("lastOpAck", "");
lastOperationAck.setValue("<span style='color: #0099D3;'>" + MSG.common_button_ack() + "</span>");
lastOperationAck.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
ackFailedOperation(storageNode);
}
});
formItems.add(lastOperationAck);
}
form.setItems(formItems.toArray(new FormItem[formItems.size()]));
return form;
}
private void ackFailedOperation(final StorageNode storageNode) {
GWTServiceLookup.getStorageService().ackFailedOperation(storageNodeId, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
Message message = new Message(MSG.view_adminTopology_storageNodes_detail_loadDataFetchFail(),
Message.Severity.Warning);
CoreGUI.getMessageCenter().notify(message);
}
@Override
public void onSuccess(Void result) {
storageNode.setFailedOperation(null);
storageNode.setErrorMessage(null);
detailsLayout.removeMember(detailsLayout.getMember(0));
detailsLayout.addMember(buildDetailsForm(storageNode));
if (null != jmxPort) {
jmxPortItem.setValue(jmxPort);
}
}
});
}
private void prepareLoadSection(SectionStack stack, final StorageNode storageNode,
final Map<String, List<MeasurementDataNumericHighLowComposite>> sparkLineData) {
StorageNodeLoadComponent loadDataComponent = new StorageNodeLoadComponent(storageNode.getId(), sparkLineData);
loadDataComponent.setExtraSpace(5);
loadLayout = new EnhancedVLayout();
loadLayout.setWidth("*");
LayoutSpacer spacer = new LayoutSpacer();
spacer.setHeight(10);
HTMLFlow loadLabel = new HTMLFlow(MSG.view_adminTopology_storageNodes_detail_status());
loadLabel.addStyleName("formTitle");
loadLabel.setTooltip(MSG.view_adminTopology_storageNodes_detail_loadHover());
loadLabel.setHoverWidth(300);
loadLayout.setMembers(spacer, loadLabel, loadDataComponent);
loadDataComponent.redraw();
if (detailsAndLoadLayout == null) {
detailsAndLoadLayout = new EnhancedHLayout();
}
initSectionCount++;
}
private void prepareResourceConfigEditor(final StorageNodeConfigurationComposite configuration) {
LayoutSpacer spacer = new LayoutSpacer();
spacer.setHeight(15);
StorageNodeConfigurationEditor editorView = new StorageNodeConfigurationEditor(configuration);
SectionStackSection section = new SectionStackSection(
MSG.view_adminTopology_storageNodes_detail_configuration());
section.setItems(spacer, editorView);
section.setExpanded(true);
section.setCanCollapse(false);
configurationSection = section;
initSectionCount++;
}
private void showAlertsForSingleStorageNode() {
GWTServiceLookup.getStorageService().findResourcesWithAlertDefinitions(new StorageNode(storageNodeId),
new AsyncCallback<Integer[]>() {
@Override
public void onFailure(Throwable caught) {
alerts = false;
CoreGUI.getErrorHandler().handleError(
MSG.view_adminTopology_storageNodes_detail_errorAlertFetch(String.valueOf(storageNodeId))
+ caught.getMessage(), caught);
}
@Override
public void onSuccess(Integer[] result) {
if (result == null || result.length == 0) {
onFailure(new Exception(MSG.view_adminTopology_storageNodes_detail_errorNoResourcesWithAlerts()));
} else {
removeMember(sectionStack);
sectionStack.destroy();
int[] resIds = ArrayUtils.unwrapArray(result);
Canvas alertsView = new StorageNodeAlertHistoryView("storageNode_" + storageNodeId + "_Alerts",
resIds, header, storageNodeId);
addMember(alertsView);
}
}
});
}
@Override
public void renderView(ViewPath viewPath) {
if (viewPath.toString().endsWith("/Alerts")) {
alerts = true;
showAlertsForSingleStorageNode();
} else {
alerts = false;
}
Log.debug("StorageNodeDetailView: " + viewPath);
}
}