/******************************************************************************* *Copyright (c) 2009 Eucalyptus Systems, Inc. * * 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, only version 3 of the License. * * * This file 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, see <http://www.gnu.org/licenses/>. * * Please contact Eucalyptus Systems, Inc., 130 Castilian * Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/> * if you need additional information or have any questions. * * This file may incorporate work covered under the following copyright and * permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, with * or without modification, are permitted provided that the following * conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF * THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE * LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS * SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA * BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN * THE REGENTS’ DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT * OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR * WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH * ANY SUCH LICENSES OR RIGHTS. *******************************************************************************/ package edu.ucsb.eucalyptus.admin.client; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.*; import java.util.ArrayList; import java.util.List; // dmitrii TODO: remove commented out lines once the CSS-based design is confirmed public class ClusterInfoTable extends VerticalPanel implements ClickListener { private static String warningMessage = "Note: adding a cluster requires synchronization of keys among all nodes, which cannot be done through this interface. See documentation for details."; private static int maxClusters = 4096; //arbitrary private Label noClusterLabel = new Label(); private Label statusLabel = new Label(); private Grid grid = new Grid (); private Button add_button = new Button ( "Register cluster", this ); private HTML hint = new HTML (); private List<ClusterInfoWeb> clusterList = new ArrayList<ClusterInfoWeb>(); private List<StorageInfoWeb> storageList = new ArrayList<StorageInfoWeb>(); private SystemConfigWeb systemConfig = new SystemConfigWeb (); private String sessionId; private int numStorageParams; public ClusterInfoTable(String sessionId) { this.sessionId = sessionId; this.setStyleName("euca-config-component"); this.setSpacing (2); this.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); // this.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); Label clustersHeader = new Label( "Clusters:" ); clustersHeader.setStyleName ( "euca-section-header" ); this.add ( clustersHeader ); this.noClusterLabel.setText ("No clusters registered"); this.noClusterLabel.setStyleName ("euca-greeting-disabled"); HorizontalPanel grid_and_hint = new HorizontalPanel (); grid_and_hint.add ( this.grid ); grid_and_hint.add ( this.hint ); this.hint.setWidth ("100"); this.add ( grid_and_hint ); HorizontalPanel hpanel = new HorizontalPanel (); hpanel.setSpacing (2); hpanel.add ( add_button ); hpanel.add ( new Button( "Save cluster configuration", new SaveCallback( this ) ) ); hpanel.add ( this.statusLabel ); // this.statusLabel.setWidth ("250"); this.statusLabel.setText (""); this.statusLabel.setStyleName ("euca-greeting-pending"); this.add ( hpanel ); rebuildTable(); EucalyptusWebBackend.App.getInstance().getClusterList( this.sessionId, new GetClusterListCallback( this ) ); EucalyptusWebBackend.App.getInstance().getSystemConfig( this.sessionId, new GetSystemConfigCallback( this ) ); EucalyptusWebBackend.App.getInstance().getStorageList( this.sessionId, new GetStorageListCallback(this)); } public void onClick( final Widget widget ) // Add cluster button { this.clusterList.add (new ClusterInfoWeb ("cluster-name", "cc-host", 8774, 10, 4096)); //these values are just defaults this.storageList.add (new StorageInfoWeb("sc-name", "sc-host", 8773, new ArrayList<String>())); this.rebuildTable(); this.statusLabel.setText ("Unsaved changes"); this.statusLabel.setStyleName ("euca-greeting-warning"); } private void rebuildTable() { if (this.clusterList.isEmpty()) { this.grid.setVisible (false); this.noClusterLabel.setVisible (true); this.add_button.setEnabled (true); } else { this.noClusterLabel.setVisible (false); this.grid.clear (); this.grid.resize ( this.clusterList.size(), 1 ); this.grid.setVisible (true); this.grid.setStyleName( "euca-table" ); this.grid.setCellPadding( 2 ); int row = 0; for ( ClusterInfoWeb cluster : this.clusterList ) { /*// big yellow block looks kinda weird if ( ( row % 2 ) == 1 ) { this.grid.getRowFormatter().setStyleName( row, "euca-table-even-row" ); } else { this.grid.getRowFormatter().setStyleName( row, "euca-table-odd-row" ); }*/ StorageInfoWeb storageInfo = this.storageList.get(row); this.grid.setWidget (row, 0, addClusterEntry (row++, cluster, storageInfo)); } if ( row >= maxClusters ) { this.add_button.setEnabled (false); } else { this.add_button.setEnabled (true); } } } private Grid addClusterEntry ( int row, ClusterInfoWeb clusterInfo, final StorageInfoWeb storageInfo) { final ArrayList<String> storageParams = storageInfo.getStorageParams(); numStorageParams = storageParams.size()/4; Grid g = new Grid (8 + numStorageParams, 2); g.setStyleName( "euca-table" ); if (row > 0) { g.setStyleName( "euca-nonfirst-cluster-entry" ); } g.setCellPadding( 4 ); int i = 0; // row 1 g.setWidget( i, 0, new HTML( "<b>Name:</b>" ) ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); final HorizontalPanel namePanel = new HorizontalPanel (); namePanel.setSpacing (0); if (clusterInfo.isCommitted()) { namePanel.add (new Label ( clusterInfo.getName() )); } else { final TextBox nb = new TextBox(); nb.addChangeListener (new ChangeCallback (this, row)); nb.setVisibleLength( 12 ); nb.setText( clusterInfo.getName() ); nb.addFocusListener (new FocusHandler (this.hint, this.warningMessage)); namePanel.add ( nb ); } namePanel.add (new Button ("Deregister Cluster", new DeleteCallback( this, row ))); g.setWidget ( i, 1, namePanel); i++; // next row g.setWidget( i, 1, new Label( "Cluster Controller" )); i++; // next row g.setWidget( i, 0, new Label( "Host:" ) ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); final TextBox hb = new TextBox(); hb.addChangeListener (new ChangeCallback (this, row)); hb.setVisibleLength( 20 ); hb.setText( clusterInfo.getHost() ); hb.addFocusListener (new FocusHandler (this.hint, this.warningMessage)); g.setWidget ( i, 1, hb ); i++; // next row /* g.setWidget( i, 0, new Label( "Port:" ) ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); final TextBox pb = new TextBox(); pb.addChangeListener (new ChangeCallback (this, row)); pb.setVisibleLength( 5 ); pb.setText( "" + clusterInfo.getPort() ); pb.addFocusListener (new FocusHandler (this.hint, this.warningMessage)); g.setWidget( i, 1, pb );*/ final TextBox reservedAddressesBox = new TextBox(); // declare here, set up after the checkbox later final CheckBox dynamicAddressesCheckbox = new CheckBox (); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); g.setWidget (i, 0, dynamicAddressesCheckbox ); if (systemConfig.isDoDynamicPublicAddresses()) { dynamicAddressesCheckbox.setChecked(true); reservedAddressesBox.setEnabled(false); } else { dynamicAddressesCheckbox.setChecked(false); reservedAddressesBox.setEnabled(true); } dynamicAddressesCheckbox.addClickListener (new ClickListener() { public void onClick( Widget sender ) { if (((CheckBox)sender).isChecked()) { reservedAddressesBox.setEnabled(false); systemConfig.setDoDynamicPublicAddresses( true ); } else { reservedAddressesBox.setEnabled(true); systemConfig.setDoDynamicPublicAddresses( false ); } } }); g.setWidget( i, 1, new Label ("Dynamic public IP address assignment") ); i++; // next row g.setWidget( i, 0, new Label( "Reserve for assignment" ) ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); reservedAddressesBox.addChangeListener (new ChangeCallback (this, row)); reservedAddressesBox.setVisibleLength( 5 ); reservedAddressesBox.setText( "" + systemConfig.getSystemReservedPublicAddresses()); final HorizontalPanel reservedAddressesPanel = new HorizontalPanel (); reservedAddressesPanel.setSpacing(4); reservedAddressesPanel.add (reservedAddressesBox); reservedAddressesPanel.add (new HTML ("public IP addresses")); reservedAddressesBox.setText(""+systemConfig.getSystemReservedPublicAddresses()); g.setWidget( i, 1, reservedAddressesPanel ); i++; // next row g.setWidget( i, 0, new Label( "Maximum of" ) ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); final TextBox publicAddressesBox = new TextBox(); publicAddressesBox.addChangeListener (new ChangeCallback (this, row)); publicAddressesBox.setVisibleLength( 5 ); publicAddressesBox.setText( "" + systemConfig.getMaxUserPublicAddresses()); final HorizontalPanel publicAddressesPanel = new HorizontalPanel (); publicAddressesPanel.setSpacing(4); publicAddressesPanel.add (publicAddressesBox); publicAddressesPanel.add (new HTML ("public IP addresses per user")); g.setWidget( i, 1, publicAddressesPanel ); i++; g.setWidget( i, 0, new Label( "Use VLAN tags" ) ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); final TextBox minVlanBox = new TextBox(); minVlanBox.addChangeListener (new ChangeCallback (this, row)); minVlanBox.setVisibleLength( 4 ); minVlanBox.setText(String.valueOf(clusterInfo.getMinVlans())); final TextBox maxVlanBox = new TextBox(); maxVlanBox.addChangeListener (new ChangeCallback (this, row)); maxVlanBox.setVisibleLength( 4 ); maxVlanBox.setText(String.valueOf(clusterInfo.getMaxVlans())); final HorizontalPanel vlanPanel = new HorizontalPanel (); vlanPanel.setSpacing(4); vlanPanel.add (minVlanBox); vlanPanel.add (new HTML ("through")); vlanPanel.add (maxVlanBox); g.setWidget( i, 1, vlanPanel ); i++; // next row g.setWidget( i, 1, new Label( "Storage Controller" )); for(int paramidx = 0; paramidx < numStorageParams; ++paramidx) { i++; // next row if ("KEYVALUE".equals(storageParams.get(4 * paramidx))) { g.setWidget( i, 0, new Label(storageParams.get(4*paramidx + 1) + ": ") ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); final TextBox propTextBox = new TextBox(); propTextBox.addChangeListener (new ChangeCallback (this, row)); propTextBox.setVisibleLength( 30 ); propTextBox.setText(storageParams.get(4*paramidx + 2)); propTextBox.addFocusListener (new FocusHandler (this.hint, this.warningMessage)); g.setWidget( i, 1, propTextBox ); } else if ("PASSWORD".equals(storageParams.get(4 * paramidx))) { g.setWidget( i, 0, new Label(storageParams.get(4*paramidx + 1) + ": ") ); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); final TextBox propTextBox = new PasswordTextBox(); propTextBox.addChangeListener (new ChangeCallback (this, row)); propTextBox.setVisibleLength( 30 ); propTextBox.setText(storageParams.get(4*paramidx + 2)); propTextBox.addFocusListener (new FocusHandler (this.hint, this.warningMessage)); g.setWidget( i, 1, propTextBox ); } else if("BOOLEAN".equals(storageParams.get(4 * paramidx))) { final int index = paramidx; final CheckBox propCheckbox = new CheckBox (); g.getCellFormatter().setHorizontalAlignment(i, 0, HasHorizontalAlignment.ALIGN_RIGHT); g.setWidget( i, 0, propCheckbox ); if (Boolean.parseBoolean(storageParams.get(4*index + 2))) { propCheckbox.setChecked(true); } else { propCheckbox.setChecked(false); } propCheckbox.addClickListener (new ClickListener() { public void onClick( Widget sender ) { if (((CheckBox)sender).isChecked()) { storageParams.set(4 * index + 2, String.valueOf(true) ); } else { storageParams.set(4 * index + 2, String.valueOf(false) ); } } }); g.setWidget( i, 1, new Label (storageParams.get(paramidx * 4 + 1)) ); } } return g; } public List<ClusterInfoWeb> getClusterList() { return clusterList; } public void setClusterList( final List<ClusterInfoWeb> clusterList ) { this.clusterList = clusterList; } public List<StorageInfoWeb> getStorageList() { return storageList; } public void setStorageList( final List<StorageInfoWeb> storageList ) { this.storageList = storageList; } public void updateRow (int row) { ClusterInfoWeb cluster = this.clusterList.get (row); StorageInfoWeb storage = this.storageList.get(row); Grid g = (Grid)this.grid.getWidget(row, 0); HorizontalPanel p = (HorizontalPanel)g.getWidget(0, 1); if (p.getWidget(0) instanceof TextBox) { cluster.setName (((TextBox)p.getWidget(0)).getText()); storage.setName (((TextBox)p.getWidget(0)).getText()); } else { cluster.setName (((Label)p.getWidget(0)).getText()); storage.setName (((Label)p.getWidget(0)).getText()); } // CC section cluster.setHost (((TextBox)g.getWidget(2, 1)).getText()); //cluster.setPort (Integer.parseInt(((TextBox)g.getWidget(3, 1)).getText())); p = (HorizontalPanel)g.getWidget(4, 1); systemConfig.setSystemReservedPublicAddresses(Integer.parseInt(((TextBox)p.getWidget(0)).getText())); p = (HorizontalPanel)g.getWidget(5, 1); systemConfig.setMaxUserPublicAddresses(Integer.parseInt(((TextBox)p.getWidget(0)).getText())); p = (HorizontalPanel)g.getWidget(6, 1); cluster.setMinVlans(Integer.parseInt(((TextBox)p.getWidget(0)).getText())); cluster.setMaxVlans(Integer.parseInt(((TextBox)p.getWidget(2)).getText())); //7 is SC label // SC section int widgetStartIndex = 8; ArrayList<String> storageParams = storage.getStorageParams(); for(int i = 0; i < numStorageParams; ++i) { if(storageParams.get(4*i).startsWith("KEYVALUE")) storageParams.set(4*i + 2, ((TextBox)g.getWidget(widgetStartIndex + i, 1)).getText()); } } public void MarkCommitted () { for ( ClusterInfoWeb cluster : this.clusterList ) { cluster.setCommitted (); } } class ChangeCallback implements ChangeListener, ClickListener { private ClusterInfoTable parent; private int row; ChangeCallback ( final ClusterInfoTable parent, final int row ) { this.parent = parent; this.row = row; } public void onChange (Widget sender) { this.parent.updateRow (this.row); this.parent.statusLabel.setText ("Unsaved changes"); this.parent.statusLabel.setStyleName ("euca-greeting-warning"); } public void onClick (Widget sender) { this.parent.updateRow (this.row); this.parent.statusLabel.setText ("Unsaved changes"); this.parent.statusLabel.setStyleName ("euca-greeting-warning"); } } class DeleteCallback implements ClickListener { private ClusterInfoTable parent; private int row; DeleteCallback( final ClusterInfoTable parent, final int row ) { this.parent = parent; this.row = row; } public void onClick( final Widget widget ) { this.parent.clusterList.remove (this.row); this.parent.storageList.remove(this.row); this.parent.rebuildTable(); this.parent.statusLabel.setText ("Unsaved changes"); this.parent.statusLabel.setStyleName ("euca-greeting-warning"); } } class GetClusterListCallback implements AsyncCallback { private ClusterInfoTable parent; GetClusterListCallback( final ClusterInfoTable parent ) { this.parent = parent; } public void onFailure( final Throwable throwable ) { this.parent.statusLabel.setText ("Failed to contact server!"); this.parent.statusLabel.setStyleName ("euca-greeting-error"); } public void onSuccess( final Object o ) { List<ClusterInfoWeb> newClusterList = (List<ClusterInfoWeb>) o; this.parent.statusLabel.setText ("Clusters up to date"); this.parent.statusLabel.setStyleName ("euca-greeting-disabled"); this.parent.clusterList = newClusterList; this.parent.MarkCommitted(); this.parent.rebuildTable(); } } class GetStorageListCallback implements AsyncCallback { private ClusterInfoTable parent; GetStorageListCallback( final ClusterInfoTable parent ) { this.parent = parent; } public void onFailure( final Throwable throwable ) { this.parent.statusLabel.setText ("Failed to contact server!"); this.parent.statusLabel.setStyleName ("euca-greeting-error"); } public void onSuccess( final Object o ) { List<StorageInfoWeb> newStorageList = (List<StorageInfoWeb>) o; this.parent.statusLabel.setText ("Clusters up to date"); this.parent.statusLabel.setStyleName ("euca-greeting-disabled"); this.parent.storageList = newStorageList; this.parent.MarkCommitted(); this.parent.rebuildTable(); } } class GetSystemConfigCallback implements AsyncCallback { private ClusterInfoTable parent; GetSystemConfigCallback ( final ClusterInfoTable parent ) { this.parent = parent; } public void onFailure( final Throwable throwable ) { this.parent.statusLabel.setText ("Failed to contact server!"); this.parent.statusLabel.setStyleName ("euca-greeting-error"); } public void onSuccess( final Object o ) { this.parent.systemConfig = (SystemConfigWeb) o; this.parent.rebuildTable(); } } class SaveCallback implements AsyncCallback, ClickListener { private ClusterInfoTable parent; SaveCallback( final ClusterInfoTable parent ) { this.parent = parent; } public void onClick( final Widget widget ) { this.parent.statusLabel.setText ("Saving..."); this.parent.statusLabel.setStyleName ("euca-greeting-pending"); EucalyptusWebBackend.App.getInstance().setClusterList( this.parent.sessionId, this.parent.clusterList, this ); EucalyptusWebBackend.App.getInstance().setSystemConfig( this.parent.sessionId, this.parent.systemConfig, this ); EucalyptusWebBackend.App.getInstance().setStorageList( this.parent.sessionId, this.parent.storageList, this ); } public void onFailure( final Throwable throwable ) { this.parent.statusLabel.setText ("Failed to save!"); this.parent.statusLabel.setStyleName ("euca-greeting-error"); } public void onSuccess( final Object o ) { this.parent.statusLabel.setText ("Saved clusters to server"); this.parent.statusLabel.setStyleName ("euca-greeting-disabled"); this.parent.MarkCommitted (); this.parent.rebuildTable(); // so the commmitted ones show up } } class FocusHandler implements FocusListener { private HTML parent; private String message; FocusHandler (final HTML parent, String message) { this.parent = parent; this.message = message; } public void onLostFocus (Widget sender) { this.parent.setHTML (""); this.parent.setStyleName ("euca-text"); } public void onFocus (Widget sender) { this.parent.setHTML (message); this.parent.setStyleName ("euca-error-hint"); } } }