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