/* Copyright 2011 Massimo Gengarelli <gengarel@cs.unibo.it>
* This file is part of Floz Configurator.
* Floz Configurator 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, either version 3 of the License, or
* (at your option) any later version.
*
* Floz Configurator 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 Floz Configurator. If not, see http://www.gnu.org/licenses/.
*/
package it.unibo.cs.v2.client;
import it.unibo.cs.v2.servlets.AcceptShare;
import it.unibo.cs.v2.servlets.AcceptShareAsync;
import it.unibo.cs.v2.servlets.GetActiveMachines;
import it.unibo.cs.v2.servlets.GetActiveMachinesAsync;
import it.unibo.cs.v2.servlets.GetMachines;
import it.unibo.cs.v2.servlets.GetMachinesAsync;
import it.unibo.cs.v2.servlets.GetNotifications;
import it.unibo.cs.v2.servlets.GetNotificationsAsync;
import it.unibo.cs.v2.servlets.RefuseShare;
import it.unibo.cs.v2.servlets.RefuseShareAsync;
import it.unibo.cs.v2.servlets.RemoveNotification;
import it.unibo.cs.v2.servlets.RemoveNotificationAsync;
import it.unibo.cs.v2.servlets.ShutdownMachine;
import it.unibo.cs.v2.servlets.ShutdownMachineAsync;
import it.unibo.cs.v2.shared.ExportCompleteNotification;
import it.unibo.cs.v2.shared.MachineInfo;
import it.unibo.cs.v2.shared.MachineProcessInfo;
import it.unibo.cs.v2.shared.Notification;
import it.unibo.cs.v2.shared.NotificationType;
import it.unibo.cs.v2.shared.RefuseMachineNotification;
import it.unibo.cs.v2.shared.ShareMachineNotification;
import it.unibo.cs.v2.shared.TimedNotification;
import java.util.HashMap;
import java.util.LinkedList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.StackLayoutPanel;
// This is a some sort of a Singleton
public class MainPanel extends StackLayoutPanel {
private static MainPanel _instance;
private final int HEADERSIZE = 2;
private final String WELCOME_STRING = "Welcome to <b>Live ShockVM</b>. <br/>" +
"Using this simple web app you can easily create a personal virtual machine to use whenever you want, " +
"you can also share it with your friends and see the progresses they're making with your machines. Creating a " +
"new machine is as simple as clicking some buttons and following some instructions. If you should encounter a " +
"problem, please contact <a href=\"mailto:gengarel@cs.unibo.it\">the webmaster</a>. <b>Enjoy!</b>";
private final String ZERO_NOTIFICATIONS = "<b>No new notifications</b>";
private final HTMLPanel firstPanel = new HTMLPanel(WELCOME_STRING);
private final HTMLPanel activeMachinesPanel = new HTMLPanel("<h2>Active machines</h2>");
private final HTMLPanel machines = new HTMLPanel("");
private final HTMLPanel notificationsPanel = new HTMLPanel("");
private LinkedList<Notification> notifications = new LinkedList<Notification>();
private LinkedList<MachineProcessInfo> activeMachines = new LinkedList<MachineProcessInfo>();
private final Timer notificationsTimer = new Timer() {
@Override
public void run() {
buildNotificationsList();
}
};
private final Timer activeMachinesTimer = new Timer() {
@Override
public void run() {
getActiveMachines();
};
};
private final HashMap<String, String> userInfo;
private final GetMachinesAsync getMachinesProxy = (GetMachinesAsync) GWT.create(GetMachines.class);
private final GetNotificationsAsync getNotificationsProxy = (GetNotificationsAsync) GWT.create(GetNotifications.class);
private final AcceptShareAsync acceptShareProxy = (AcceptShareAsync) GWT.create(AcceptShare.class);
private final RefuseShareAsync refuseShareProxy = (RefuseShareAsync) GWT.create(RefuseShare.class);
private final RemoveNotificationAsync removeNotificationProxy = (RemoveNotificationAsync) GWT.create(RemoveNotification.class);
private final GetActiveMachinesAsync getActiveMachinesProxy = (GetActiveMachinesAsync) GWT.create(GetActiveMachines.class);
private final ShutdownMachineAsync shutdownMachineProxy = (ShutdownMachineAsync) GWT.create(ShutdownMachine.class);
public static MainPanel getInstance() {
return _instance;
}
public void refresh() {
buildMachinesList();
}
public MainPanel(final HashMap<String, String> userInfo) {
super(Unit.EM);
_instance = this;
this.userInfo = userInfo;
final Anchor refresh = new Anchor("Refresh Machines' list");
buildMachinesList();
firstPanel.add(new HTML("<h2>Navigation Menu</h2>"));
firstPanel.add(new Hyperlink("HomePage", HistoryTokens.START));
firstPanel.add(refresh);
firstPanel.add(new HTML("<h2>Machines' management</h2>"));
firstPanel.add(new Hyperlink("Create from scratch", HistoryTokens.NEWMACHINE));
firstPanel.add(new Hyperlink("Create from an existing one", HistoryTokens.PREBUILT));
firstPanel.add(new Hyperlink("Export an existing machine", HistoryTokens.EXPORT));
firstPanel.add(activeMachinesPanel);
firstPanel.add(new HTML("<h2>Credits</h2>"));
firstPanel.add(new Anchor("VirtualSquare", "http://wiki.virtualsquare.org", "_blank"));
firstPanel.add(new HTML(" Home of VDE"));
firstPanel.add(new Anchor("KVM", "http://www.linux-kvm.org", "_blank"));
firstPanel.add(new HTML(" Kernel Virtual Machine"));
firstPanel.add(new Anchor("jBCrypt", "http://www.mindrot.org/projects/jBCrypt/", "_blank"));
firstPanel.add(new HTML(" internally used for hashing passwords"));
firstPanel.add(new Anchor("Google Web Toolkit", "http://code.google.com/webtoolkit/", "_blank"));
firstPanel.add(new HTML(" the framework used for this webapp"));
firstPanel.add(new Anchor("noVNC", "http://kanaka.github.com/noVNC", "_blank"));
firstPanel.add(new HTML(" amazing HTML5 VNC client"));
firstPanel.add(new HTML("<br /><b>Disclaimer</b><br />No Java Virtual Monkeys were harmed during the realization of Live ShockVM."));
refresh.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
buildMachinesList();
}
});
// Get notifications every 3 seconds
notificationsTimer.scheduleRepeating(3000);
// Get active machines every 3 seconds
activeMachinesTimer.scheduleRepeating(3000);
}
private final void buildMachinesList() {
clear();
add(new ScrollPanel(firstPanel), userInfo.get("displayname"), HEADERSIZE);
add(new ScrollPanel(notificationsPanel), "Sharing requests", HEADERSIZE);
machines.clear();
getMachinesProxy.getMachines(new AsyncCallback<LinkedList<MachineInfo>>() {
@Override
public void onSuccess(LinkedList<MachineInfo> result) {
if (result != null) {
for (final MachineInfo machineInfo : result) {
String machinename = machineInfo.getName();
if (!machineInfo.isUserOwner())
machinename += " (" + machineInfo.getRealOwner() + ")";
insert(new ScrollPanel(new MachinePanel(machineInfo, machineInfo.getRealOwner().equals(userInfo.get("login")))),
machinename, HEADERSIZE, getWidgetCount() - 1);
}
}
}
@Override
public void onFailure(Throwable caught) {
machines.add(new HTML("General error"));
}
});
}
// Changes the Notifications' panel accordingly
private final void buildNotificationsList() {
getNotificationsProxy.getNotifications(new AsyncCallback<LinkedList<Notification>>() {
@Override
public void onFailure(Throwable caught) {
notificationsPanel.clear();
notificationsPanel.add(new HTML("<b>Failure while getting the notifications :-(</b>"));
notificationsTimer.cancel();
}
@Override
public void onSuccess(LinkedList<Notification> result) {
if (result == null)
notificationsTimer.cancel();
// if the notifications' list has changed in some way, then refresh the panel
if (!notifications.equals(result)) {
int jobs = 0;
notifications = result;
notificationsPanel.clear();
for (final Notification notification : notifications) {
notificationsPanel.add(new HTML("<b>New notification from: " + notification.getFrom() + "</b><br />"));
switch (notification.getType()) {
case TIMEDJOB:
final TimedNotification tn = (TimedNotification) notification;
notificationsPanel.add(new HTML(tn.getTimedMessage() + "<br />"));
Double d = new Double(tn.getPercentage());
notificationsPanel.add(new HTML("<progress max=\"100\" value=\"" + d.shortValue() + "\" /><br />"));
notificationsPanel.add(new HTML(" <b>" + d.shortValue() + "%</b><br /><br />"));
jobs++;
break;
case ACCEPTEDSHARE:
case REFUSEDSHARE:
final Button read = new Button("Ok");
final RefuseMachineNotification rfn = (RefuseMachineNotification) notification;
notificationsPanel.add(new HTML(rfn.getMessage() + "<br />"));
notificationsPanel.add(read);
notificationsPanel.add(new HTML("<br /><br />"));
read.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
removeNotificationProxy.removeNotification(rfn, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
@Override
public void onSuccess(Void result) {
buildMachinesList();
}
});
}
});
break;
case IMPORTCOMPLETE:
case EXPORTCOMPLETE:
final Button exportOk = new Button("Ok");
final ExportCompleteNotification ecn = (ExportCompleteNotification) notification;
exportOk.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
removeNotificationProxy.removeNotification(ecn, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
@Override
public void onSuccess(Void result) {
buildMachinesList();
}
});
}
});
String action = notification.getType() == NotificationType.EXPORTCOMPLETE? "Export" : "Import";
notificationsPanel.add(new HTML(action + " of machine " + ecn.getMachineName() + " " + (ecn.isSucceeded()? "completed" : "gave an error")));
notificationsPanel.add(exportOk);
notificationsPanel.add(new HTML("<br />"));
break;
case SHAREMACHINE:
final Button accept = new Button("Accept");
final Button refuse = new Button("Refuse");
final HorizontalPanel container = new HorizontalPanel();
final ShareMachineNotification n = (ShareMachineNotification) notification;
notificationsPanel.add(new HTML("He/She wants to share " + n.getMachineName() + " with you.<br />"));
notificationsPanel.add(new HTML(n.getMessage() + "<br />"));
container.add(accept);
container.add(new HTML(" "));
container.add(refuse);
accept.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
acceptShareProxy.acceptShare(n, new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
@Override
public void onSuccess(Boolean result) {
if (result)
buildMachinesList();
}
});
}
});
refuse.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
refuseShareProxy.refuseShare(n, new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
@Override
public void onSuccess(Boolean result) {
if (result)
buildMachinesList();
}
});
}
});
notificationsPanel.add(container);
notificationsPanel.add(new HTML("<br /><br />"));
break;
}
}
setHeaderHTML(getWidgetCount() - 1, "Notifications (" + (notifications.size() - jobs) + ") [" + jobs + " running]");
}
else if (result.size() < 1) {
setHeaderHTML(getWidgetCount() - 1, "Notifications (0)");
notificationsPanel.clear();
notificationsPanel.add(new HTML(ZERO_NOTIFICATIONS));
}
}
});
}
public void getActiveMachines() {
getActiveMachinesProxy.getActiveMachines(new AsyncCallback<LinkedList<MachineProcessInfo>>() {
@Override
public void onFailure(Throwable caught) {
activeMachinesPanel.clear();
activeMachinesPanel.add(new HTML("<b>Failure while getting the active machines' list :-(</b>"));
}
@Override
public void onSuccess(final LinkedList<MachineProcessInfo> result) {
if (result == null) {
activeMachinesPanel.clear();
activeMachinesPanel.add(new HTML("No running machines found."));
return;
}
if (!activeMachines.equals(result)) {
activeMachines = result;
activeMachinesPanel.clear();
for (final MachineProcessInfo mp : activeMachines) {
final Anchor shutdown = new Anchor("shutdown");
shutdown.getElement().getStyle().setDisplay(Display.INLINE);
shutdown.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
shutdown.setVisible(false);
shutdownMachineProxy.shutdownMachine(mp, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
final FlozDialogBox errorBox = new FlozDialogBox("Error while shutting down.");
Button closeDialog = new Button("Roger");
closeDialog.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
errorBox.hide();
}
});
errorBox.add(new HTML(caught.getMessage()));
errorBox.addButton(closeDialog);
errorBox.show();
}
@Override
public void onSuccess(Void result) {
/* Do nothing */
}
});
}
});
HTML machineDesc = new HTML("<b>" + mp.getMachineName() + "</b> on server " + mp.getVncServer() + " ");
machineDesc.getElement().getStyle().setDisplay(Display.INLINE);
activeMachinesPanel.add(machineDesc);
activeMachinesPanel.add(shutdown);
activeMachinesPanel.add(new HTML());
}
}
}
});
}
}