You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2016/04/14 13:46:53 UTC

svn commit: r1739063 [2/3] - in /openmeetings/application/branches/3.2.x: ./ openmeetings-core/src/main/java/org/apache/openmeetings/core/data/whiteboard/ openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/ openmeetings-core/src/main/j...

Modified: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html?rev=1739063&r1=1739062&r2=1739063&view=diff
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html (original)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html Thu Apr 14 11:46:52 2016
@@ -19,16 +19,22 @@
   
 -->
 <html xmlns:wicket="http://wicket.apache.org">
+<wicket:head>
+	<script type="text/javascript">
+		Wicket.Event.subscribe("/websocket/closed", function(jqEvent, msg) {
+			//FIXME add Error dialog should be displayed 
+		});
+	</script>
+</wicket:head>
 <wicket:panel>
-	<div id="swfloading"
-		style="z-index: 666666; position: absolute; top: 50%; left: 50%;">
-		<img src="images/ajax-loader.gif" />
+	<div wicket:id="roomContainer" style="height: 100%">
+		<div class="room menu" wicket:id="roomMenu"></div>
+		<div class="room sidebar left" wicket:id="sidebar"></div>
+		<div class="room wb area">
+			<div class="wb" wicket:id="whiteboard"></div>
+		</div>
+		<div wicket:id="activitiesPanel"></div>
 	</div>
-	<noscript>Please enable JavaScript in order to use this application.</noscript>
-	<script type="text/javascript" wicket:id="init"></script>
-	<div wicket:id="invite"></div>
-	<div wicket:id="createPoll"></div>
-	<div wicket:id="vote"></div>
-	<div wicket:id="pollResults"></div>
+	<div wicket:id="accessDenied"></div>
 </wicket:panel>
-</html>
\ No newline at end of file
+</html>

Modified: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java?rev=1739063&r1=1739062&r2=1739063&view=diff
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java (original)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java Thu Apr 14 11:46:52 2016
@@ -19,280 +19,335 @@
 package org.apache.openmeetings.web.room;
 
 import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+import static org.apache.openmeetings.web.app.Application.addUserToRoom;
 import static org.apache.openmeetings.web.app.Application.getBean;
 import static org.apache.openmeetings.web.app.Application.getRoomUsers;
-import static org.apache.openmeetings.web.app.WebSession.getLanguage;
-import static org.apache.openmeetings.web.app.WebSession.getSid;
 import static org.apache.openmeetings.web.app.WebSession.getUserId;
-import static org.apache.openmeetings.web.room.RoomBroadcaster.getClient;
-import static org.apache.openmeetings.web.util.CallbackFunctionHelper.getNamedFunction;
-import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.net.MalformedURLException;
+import java.net.URL;
 
-import org.apache.openmeetings.core.session.SessionManager;
-import org.apache.openmeetings.db.dao.room.InvitationDao;
-import org.apache.openmeetings.db.dao.room.PollDao;
-import org.apache.openmeetings.db.dao.room.RoomDao;
-import org.apache.openmeetings.db.dao.server.SOAPLoginDao;
-import org.apache.openmeetings.db.dao.server.ServerDao;
-import org.apache.openmeetings.db.dao.server.SessiondataDao;
-import org.apache.openmeetings.db.entity.room.Client;
-import org.apache.openmeetings.db.entity.server.SOAPLogin;
-import org.apache.openmeetings.db.entity.server.Server;
-import org.apache.openmeetings.web.app.WebSession;
+import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
+import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.dao.user.UserDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.MeetingMember;
+import org.apache.openmeetings.db.entity.room.Room;
+import org.apache.openmeetings.db.entity.room.RoomGroup;
+import org.apache.openmeetings.db.entity.room.RoomModerator;
+import org.apache.openmeetings.db.entity.user.GroupUser;
+import org.apache.openmeetings.db.entity.user.User;
+import org.apache.openmeetings.db.util.AuthLevelUtil;
+import org.apache.openmeetings.web.app.Application;
+import org.apache.openmeetings.web.app.Client;
 import org.apache.openmeetings.web.common.BasePanel;
-import org.apache.openmeetings.web.room.poll.CreatePollDialog;
-import org.apache.openmeetings.web.room.poll.PollResultsDialog;
-import org.apache.openmeetings.web.room.poll.VoteDialog;
+import org.apache.openmeetings.web.room.activities.ActivitiesPanel;
+import org.apache.openmeetings.web.room.activities.Activity;
+import org.apache.openmeetings.web.room.menu.RoomMenuPanel;
+import org.apache.openmeetings.web.room.message.RoomMessage;
+import org.apache.openmeetings.web.room.sidebar.RoomSidebar;
 import org.apache.wicket.Component;
-import org.apache.wicket.RuntimeConfigurationType;
-import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
 import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
 import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
-import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.event.IEvent;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
 import org.apache.wicket.markup.head.PriorityHeaderItem;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.protocol.ws.WebSocketSettings;
+import org.apache.wicket.protocol.ws.api.IWebSocketConnection;
+import org.apache.wicket.protocol.ws.api.event.WebSocketPushPayload;
+import org.apache.wicket.protocol.ws.api.registry.IWebSocketConnectionRegistry;
+import org.apache.wicket.protocol.ws.api.registry.PageIdKey;
+import org.apache.wicket.protocol.ws.concurrent.Executor;
 import org.apache.wicket.request.resource.JavaScriptResourceReference;
 import org.apache.wicket.request.resource.ResourceReference;
-import org.apache.wicket.util.string.StringValue;
-import org.apache.wicket.util.time.Duration;
 import org.red5.logging.Red5LoggerFactory;
 import org.slf4j.Logger;
 
+import com.googlecode.wicket.jquery.core.JQueryBehavior;
+import com.googlecode.wicket.jquery.ui.widget.dialog.DialogButton;
+import com.googlecode.wicket.jquery.ui.widget.dialog.DialogButtons;
+import com.googlecode.wicket.jquery.ui.widget.dialog.DialogIcon;
+import com.googlecode.wicket.jquery.ui.widget.dialog.MessageDialog;
+
+@AuthorizeInstantiation("Room")
 public class RoomPanel extends BasePanel {
+	//TODO demoTime - demo timer
 	private static final long serialVersionUID = 1L;
-	private static final String WICKET_ROOM_ID = "wicketroomid";
-	public static final String PARAM_PUBLIC_SID = "publicSid";
-	public static final String PARAM_URL = "url";
 	private static final Logger log = Red5LoggerFactory.getLogger(RoomPanel.class, webAppRootKey);
-	private final InvitationDialog invite;
-	private final CreatePollDialog createPoll;
-	private final VoteDialog vote;
-	private final PollResultsDialog pollResults;
-	private final StartSharingEventBehavior startSharing;
-	private Long roomId = null;
-	
-	public RoomPanel(String id) {
-		this(id, new PageParameters());
-	}
-	
-	private String getFlashFile() {
-		return RuntimeConfigurationType.DEVELOPMENT == getApplication().getConfigurationType()
-				? "maindebug.swf11.swf" : "main.swf11.swf";
-	}
-	
-	private static PageParameters addServer(PageParameters pp, Server s) {
-		return pp.add("protocol", s.getProtocol()).add("host", s.getAddress()).add("port", s.getPort()).add("context", s.getWebapp());
-	}
-	
-	public static PageParameters addServer(Long roomId, boolean addBasic) {
-		PageParameters pp = new PageParameters();
-		if (addBasic) {
-			pp.add("wicketsid", getSid()).add(WICKET_ROOM_ID, roomId).add("language", getLanguage());
-		}
-		List<Server> serverList = getBean(ServerDao.class).getActiveServers();
+	private final Room r;
+	private final Client client;
+	private final RoomMenuPanel menu;
+	private final RoomSidebar sidebar;
+	private final WebMarkupContainer room = new WebMarkupContainer("roomContainer");
+	private final AbstractDefaultAjaxBehavior aab = new AbstractDefaultAjaxBehavior() {
+		private static final long serialVersionUID = 1L;
 
-		long minimum = -1;
-		Server result = null;
-		HashMap<Server, List<Long>> activeRoomsMap = new HashMap<Server, List<Long>>();
-		for (Server server : serverList) {
-			List<Long> roomIds = getBean(SessionManager.class).getActiveRoomIdsByServer(server);
-			if (roomIds.contains(roomId)) {
-				// if the room is already opened on a server, redirect the user to that one,
-				log.debug("Room is already opened on a server " + server.getAddress());
-				return addServer(pp, server);
+		@Override
+		protected void respond(AjaxRequestTarget target) {
+			target.appendJavaScript("setHeight();");
+			//TODO SID etc
+			ConfigurationDao cfgDao = getBean(ConfigurationDao.class);
+			try {
+				URL url = new URL(cfgDao.getBaseUrl());
+				String path = url.getPath();
+				path = path.substring(1, path.indexOf('/', 2) + 1);
+				broadcast(new RoomMessage(r.getId(), RoomMessage.Type.roomEnter));
+				getMainPage().getChat().roomEnter(r, target);
+			} catch (MalformedURLException e) {
+				log.error("Error while constructing room parameters", e);
 			}
-			activeRoomsMap.put(server, roomIds);
 		}
-		for (Map.Entry<Server, List<Long>> entry : activeRoomsMap.entrySet()) {
-			List<Long> roomIds = entry.getValue();
-			long capacity = getBean(RoomDao.class).getRoomsCapacityByIds(roomIds);
-			if (minimum < 0 || capacity < minimum) {
-				minimum = capacity;
-				result = entry.getKey();
-			}
-			log.debug("Checking server: " + entry.getKey() + " Number of rooms " + roomIds.size() + " RoomIds: "
-					+ roomIds + " max(Sum): " + capacity);
-		}
-		return result == null ? pp : addServer(pp, result);
-	}
-
-	public RoomPanel(String id, Long roomId) {
-		this(id, addServer(roomId, true));
-	}
+	};
+	private final ActivitiesPanel activities;
 	
-	public RoomPanel(String id, PageParameters pp) {
+	public RoomPanel(String id, Room r) {
 		super(id);
-		//OK let's find the room
-		try {
-			StringValue room = pp.get(WICKET_ROOM_ID);
-			StringValue secureHash = pp.get(WebSession.SECURE_HASH);
-			StringValue invitationHash = pp.get(WebSession.INVITATION_HASH);
-			if (!room.isEmpty()) {
-				roomId = room.toLongObject();
-			} else if (!secureHash.isEmpty()) {
-				if (WebSession.get().signIn(secureHash.toString(), false)) {
-					SOAPLogin soapLogin = getBean(SOAPLoginDao.class).get(secureHash.toString());
-					roomId = soapLogin.getRoomId();
-					pp = pp.mergeWith(RoomPanel.addServer(roomId, false));
-				}
-				//TODO access denied
-			} else if (!invitationHash.isEmpty()) {
-				roomId = getBean(InvitationDao.class).getInvitationByHashCode(invitationHash.toString(), true).getRoom().getId();
-			}
-		} catch (Exception e) {
-			//no-op
-		}
-		StringValue swfVal = pp.get("swf");
-		PageParameters spp = new PageParameters(pp);
-		if (roomId != null) {
-			spp.mergeWith(new PageParameters().add(WICKET_ROOM_ID, roomId));
-		}
-		String swf = (swfVal.isEmpty() ? getFlashFile() : swfVal.toString())
-				+ new PageParametersEncoder().encodePageParameters(spp);
-		add(new Label("init", String.format("initSwf('%s');", swf)).setEscapeModelStrings(false));
-		add(new AbstractAjaxTimerBehavior(Duration.minutes(5)) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void onTimer(AjaxRequestTarget target) {
-				getBean(SessiondataDao.class).checkSession(WebSession.getSid()); //keep SID alive
-			}
-		});
-		add(invite = new InvitationDialog("invite", roomId));
-		add(createPoll = new CreatePollDialog("createPoll", roomId));
-		add(vote = new VoteDialog("vote", roomId));
-		add(pollResults = new PollResultsDialog("pollResults", roomId));
-		add(startSharing = new StartSharingEventBehavior(roomId));
-		if (roomId != null && roomId.longValue() > 0) {
-			add(new AbstractDefaultAjaxBehavior() {
-				private static final long serialVersionUID = 1L;
-	
-				@Override
-				protected void respond(AjaxRequestTarget target) {
-					invite.updateModel(target);
-					invite.open(target);
-				}
-				
-				@Override
-				public void renderHead(Component component, IHeaderResponse response) {
-					super.renderHead(component, response);
-					response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forScript(getNamedFunction("openInvitation", this), "openInvitation")));
-				}
-			});
-			add(new AbstractDefaultAjaxBehavior() {
-				private static final long serialVersionUID = 1L;
-	
-				@Override
-				protected void respond(AjaxRequestTarget target) {
-					String publicSid = getPublicSid();
-					Client c = getClient(publicSid);
-					if (c != null && c.getIsMod()) {
-						createPoll.updateModel(target, publicSid);
-						createPoll.open(target);
+		this.r = r;
+		Component accessDenied = new WebMarkupContainer("accessDenied").setVisible(false);
+		boolean allowed = false;
+		String deniedMessage = null;
+		if (r.isAppointment()) {
+			Appointment a = getBean(AppointmentDao.class).getByRoom(r.getId());
+			if (a != null && !a.isDeleted()) {
+				allowed = a.getOwner().getId().equals(getUserId());
+				log.debug("appointed room, isOwner ? " + allowed);
+				if (!allowed) {
+					for (MeetingMember mm : a.getMeetingMembers()) {
+						if (mm.getUser().getId() == getUserId()) {
+							allowed = true;
+							break;
+						}
 					}
 				}
-				
-				@Override
-				public void renderHead(Component component, IHeaderResponse response) {
-					super.renderHead(component, response);
-					response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forScript(getNamedFunction("createPoll", this, explicit(PARAM_PUBLIC_SID)), "createPoll")));
+				/*
+				TODO need to be reviewed
+				Calendar c = WebSession.getCalendar();
+				if (c.getTime().after(a.getStart()) && c.getTime().before(a.getEnd())) {
+					allowed = true;
+				} else {
+					SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm"); //FIXME format
+					deniedMessage = Application.getString(1271) + String.format(" %s - %s", sdf.format(a.getStart()), sdf.format(a.getEnd()));
 				}
-			});
-			add(new AbstractDefaultAjaxBehavior() {
-				private static final long serialVersionUID = 1L;
-	
-				@Override
-				protected void respond(AjaxRequestTarget target) {
-					Client c = getClient(getPublicSid());
-					if (c != null) {
-						pollResults.updateModel(target, c.getIsMod());
-						pollResults.open(target);
+				*/
+			}
+		} else {
+			allowed = r.getIspublic() || (r.getOwnerId() != null && r.getOwnerId().equals(getUserId()));
+			log.debug("public ? " + r.getIspublic() + ", ownedId ? " + r.getOwnerId() + " " + allowed);
+			if (!allowed) {
+				User u = getBean(UserDao.class).get(getUserId());
+				for (RoomGroup ro : r.getRoomGroups()) {
+					for (GroupUser ou : u.getGroupUsers()) {
+						if (ro.getGroup().getId().equals(ou.getGroup().getId())) {
+							allowed = true;
+							break;
+						}
 					}
-				}
-				
-				@Override
-				public void renderHead(Component component, IHeaderResponse response) {
-					super.renderHead(component, response);
-					response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forScript(getNamedFunction("pollResults", this, explicit(PARAM_PUBLIC_SID)), "pollResults")));
-				}
-			});
-			add(new AbstractDefaultAjaxBehavior() {
-				private static final long serialVersionUID = 1L;
-	
-				@Override
-				protected void respond(AjaxRequestTarget target) {
-					if (getBean(PollDao.class).hasPoll(roomId) && !getBean(PollDao.class).hasVoted(roomId, getUserId()) && getClient(getPublicSid()) != null) {
-						vote.updateModel(target);
-						vote.open(target);
+					if (allowed) {
+						break;
 					}
 				}
-				
-				@Override
-				public void renderHead(Component component, IHeaderResponse response) {
-					super.renderHead(component, response);
-					response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forScript(getNamedFunction("vote", this, explicit(PARAM_PUBLIC_SID)), "vote")));
+			}
+		}
+		if (!allowed) {
+			if (deniedMessage == null) {
+				deniedMessage = Application.getString(1599);
+			}
+			accessDenied = new ExpiredMessageDialog("accessDenied", deniedMessage);
+			room.setVisible(false);
+		}
+		client = new Client(r.getId());
+		room.add((menu = new RoomMenuPanel("roomMenu", this)).setVisible(!r.getHideTopBar()));
+		room.add(new SwfPanel("whiteboard", client));
+		room.add(aab);
+		room.add(sidebar = new RoomSidebar("sidebar", this));
+		room.add((activities = new ActivitiesPanel("activitiesPanel", r.getId())).setVisible(!r.isActivitiesHidden()));
+		add(room, accessDenied);
+	}
+
+	@Override
+	public void onEvent(IEvent<?> event) {
+		if (event.getPayload() instanceof WebSocketPushPayload) {
+			WebSocketPushPayload wsEvent = (WebSocketPushPayload) event.getPayload();
+			if (wsEvent.getMessage() instanceof RoomMessage) {
+				RoomMessage m = (RoomMessage)wsEvent.getMessage();
+				IPartialPageRequestHandler handler = wsEvent.getHandler();
+				switch (m.getType()) {
+					case pollCreated:
+						if (getUserId() != m.getUserId()) {
+							menu.pollCreated(handler);
+						}
+					case pollClosed:
+					case pollDeleted:
+					case voted:
+					case rightUpdated:
+						menu.update(handler);
+						break;
+					case roomEnter:
+						menu.update(handler);
+						sidebar.updateUsers(handler);
+						//activities.addActivity(m.getUid(), m.getSentUserId(), Activity.Type.roomEnter, handler);
+						break;
+					case roomExit:
+						//TODO check user/remove tab
+						sidebar.updateUsers(handler);
+						activities.addActivity(m.getUid(), m.getUserId(), Activity.Type.roomExit, handler);
+						break;
+					case requestRightModerator:
+						if (isModerator(getUserId(), r.getId())) {
+							activities.addActivity(m.getUid(), m.getUserId(), Activity.Type.requestRightModerator, handler);
+						}
+						break;
+					default:
+						break;
 				}
-			});
-			add(new AbstractDefaultAjaxBehavior() {
-				private static final long serialVersionUID = 1L;
-	
-				@Override
-				protected void respond(AjaxRequestTarget target) {
-					startSharing.respond(target);
+			}
+		}
+		super.onEvent(event);
+	}
+	
+	@Override
+	protected void onBeforeRender() {
+		super.onBeforeRender();
+		if (room.isVisible()) {
+			addUserToRoom(client, getPage().getPageId());
+			User u = getBean(UserDao.class).get(getUserId());
+			//TODO do we need to check GroupModerationRights ????
+			if (AuthLevelUtil.hasAdminLevel(u.getRights())) {
+				client.getRights().add(Client.Right.moderator);
+			} else {
+				if (!r.isModerated() && 1 == getRoomUsers(r.getId()).size()) {
+					client.getRights().add(Client.Right.moderator);
+				} else if (r.isModerated()) {
+					//TODO why do we need supermoderator ????
+					for (RoomModerator rm : r.getModerators()) {
+						if (getUserId() == rm.getUser().getId()) {
+							client.getRights().add(Client.Right.moderator);
+							break;
+						}
+					}
 				}
-				
-				@Override
-				public void renderHead(Component component, IHeaderResponse response) {
-					super.renderHead(component, response);
-					response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forScript(getNamedFunction("startSharing", this, explicit(PARAM_PUBLIC_SID), explicit(PARAM_URL)), "startSharing")));
+			}
+		}
+	}
+	
+	public static void broadcast(final RoomMessage m) {
+		WebSocketSettings settings = WebSocketSettings.Holder.get(Application.get());
+		IWebSocketConnectionRegistry reg = settings.getConnectionRegistry();
+		Executor executor = settings.getWebSocketPushMessageExecutor();
+		for (Client c : getRoomUsers(m.getRoomId())) {
+			try {
+				final IWebSocketConnection wsConnection = reg.getConnection(Application.get(), c.getSessionId(), new PageIdKey(c.getPageId()));
+				if (wsConnection != null) {
+					executor.run(new Runnable() {
+						@Override
+						public void run() {
+							wsConnection.sendMessage(m);
+						}
+					});
 				}
-			});
+			} catch (Exception e) {
+				log.error("Error while broadcasting message to room", e);
+			}
 		}
 	}
-
+	
 	public static boolean isModerator(long userId, long roomId) {
-		for (org.apache.openmeetings.web.app.Client c : getRoomUsers(roomId)) {
-			if (c.getUserId() == userId && c.hasRight(org.apache.openmeetings.web.app.Client.Right.moderator)) {
+		for (Client c : getRoomUsers(roomId)) {
+			if (c.getUserId() == userId && c.hasRight(Client.Right.moderator)) {
 				return true;
 			}
 		}
 		return false;
 	}
 	
+	public static void sendRoom(long roomId, String msg) {
+		IWebSocketConnectionRegistry reg = WebSocketSettings.Holder.get(Application.get()).getConnectionRegistry();
+		for (Client c : getRoomUsers(roomId)) {
+			try {
+				reg.getConnection(Application.get(), c.getSessionId(), new PageIdKey(c.getPageId())).sendMessage(msg);
+			} catch (Exception e) {
+				log.error("Error while sending message to room", e);
+			}
+		}
+	}
+	
 	@Override
 	public void onMenuPanelLoad(IPartialPageRequestHandler handler) {
-		handler.add(getMainPage().getHeader().setVisible(false), getMainPage().getMenu().setVisible(false)
-				, getMainPage().getTopLinks().setVisible(false));
-		//handler.appendJavaScript("roomLoad();");
+		handler.add(getMainPage().getHeader().setVisible(false), getMainPage().getTopControls().setVisible(false));
+		if (r.isChatHidden()) {
+			getMainPage().getChat().toggle(handler, false);
+		}
+		handler.appendJavaScript("roomLoad();");
 	}
 	
+	@Override
+	public void cleanup(IPartialPageRequestHandler handler) {
+		handler.add(getMainPage().getHeader().setVisible(true), getMainPage().getTopControls().setVisible(true));
+		if (r.isChatHidden()) {
+			getMainPage().getChat().toggle(handler, true);
+		}
+		handler.appendJavaScript("$(window).off('resize.openmeetings');");
+		RoomMenuPanel.roomExit(this);
+		getMainPage().getChat().roomExit(r, handler);
+	}
+
 	private static ResourceReference newResourceReference() {
-		return new JavaScriptResourceReference(RoomPanel.class, "swf-functions.js");
+		return new JavaScriptResourceReference(RoomPanel.class, "room.js");
 	}
 	
 	@Override
 	public void renderHead(IHeaderResponse response) {
 		super.renderHead(response);
 		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(newResourceReference())));
-		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forUrl("js/history.js")));
-		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forUrl("js/openmeetings_functions.js")));
-		response.render(new PriorityHeaderItem(CssHeaderItem.forUrl("css/history.css")));
-		//FIXME TODO ugly HACK
-		if (WebSession.get().getClientInfo().getProperties().isBrowserMozillaFirefox()) {
-			response.render(new PriorityHeaderItem(CssHeaderItem.forCSS(".ui-widget-overlay{opacity: 1 !important;}", "linux-ff-veil-hack")));
+		if (room.isVisible()) {
+			response.render(OnDomReadyHeaderItem.forScript(aab.getCallbackScript()));
+		}
+	}
+
+	class ExpiredMessageDialog extends MessageDialog {
+		private static final long serialVersionUID = 1L;
+		public boolean autoOpen = false;
+		
+		public ExpiredMessageDialog(String id, String message) {
+			super(id, Application.getString(204), message, DialogButtons.OK, DialogIcon.ERROR);
+			autoOpen = true;
+		}
+		
+		@Override
+		public boolean isModal() {
+			return true;
 		}
+		
+		@Override
+		public void onConfigure(JQueryBehavior behavior) {
+			super.onConfigure(behavior);
+			behavior.setOption("autoOpen", autoOpen);
+		}
+		
+		@Override
+		public void onClose(IPartialPageRequestHandler handler, DialogButton button) {
+			menu.exit(handler);
+		}
+	}
+	
+	public Room getRoom() {
+		return r;
 	}
 	
-	private String getPublicSid() {
-		return getRequest().getRequestParameters().getParameterValue(PARAM_PUBLIC_SID).toString();
+	public Client getClient() {
+		return client;
+	}
+	
+	public RoomSidebar getSidebar() {
+		return sidebar;
+	}
+
+	public ActivitiesPanel getActivities() {
+		return activities;
 	}
 }

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.html
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.html?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.html (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.html Thu Apr 14 11:46:52 2016
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+      http://www.apache.org/licenses/LICENSE-2.0
+    	  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  
+-->
+<html xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+	<div id="swfloading" style="z-index: 666666; position: absolute; top: 50%; left: 50%;">
+		<img src="images/ajax-loader.gif" />
+	</div>
+	<noscript>Please enable JavaScript in order to use this application.</noscript>
+	<script type="text/javascript" wicket:id="init"></script>
+</wicket:panel>
+</html>

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.java
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.java?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.java (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/SwfPanel.java Thu Apr 14 11:46:52 2016
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web.room;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+import static org.apache.openmeetings.web.app.Application.getBean;
+import static org.apache.openmeetings.web.app.WebSession.getLanguage;
+import static org.apache.openmeetings.web.app.WebSession.getSid;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.openmeetings.core.session.SessionManager;
+import org.apache.openmeetings.db.dao.room.InvitationDao;
+import org.apache.openmeetings.db.dao.room.RoomDao;
+import org.apache.openmeetings.db.dao.server.SOAPLoginDao;
+import org.apache.openmeetings.db.dao.server.ServerDao;
+import org.apache.openmeetings.db.dao.server.SessiondataDao;
+import org.apache.openmeetings.db.entity.server.SOAPLogin;
+import org.apache.openmeetings.db.entity.server.Server;
+import org.apache.openmeetings.web.app.Client;
+import org.apache.openmeetings.web.app.WebSession;
+import org.apache.openmeetings.web.common.BasePanel;
+import org.apache.wicket.RuntimeConfigurationType;
+import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.PriorityHeaderItem;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.string.StringValue;
+import org.apache.wicket.util.time.Duration;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+public class SwfPanel extends BasePanel {
+	private static final long serialVersionUID = 1L;
+	private static final String WICKET_ROOM_ID = "wicketroomid";
+	public static final String PARAM_PUBLIC_SID = "publicSid";
+	public static final String PARAM_URL = "url";
+	private static final Logger log = Red5LoggerFactory.getLogger(SwfPanel.class, webAppRootKey);
+	private Long roomId = null;
+	
+	public SwfPanel(String id) {
+		this(id, new PageParameters());
+	}
+	
+	public SwfPanel(String id, Client c) {
+		this(id, addServer(c.getRoomId(), true).add("uid", c.getUid()));
+	}
+	
+	public SwfPanel(String id, PageParameters pp) {
+		super(id);
+		//OK let's find the room
+		try {
+			StringValue room = pp.get(WICKET_ROOM_ID);
+			StringValue secureHash = pp.get(WebSession.SECURE_HASH);
+			StringValue invitationHash = pp.get(WebSession.INVITATION_HASH);
+			if (!room.isEmpty()) {
+				roomId = room.toLongObject();
+			} else if (!secureHash.isEmpty()) {
+				if (WebSession.get().signIn(secureHash.toString(), false)) {
+					SOAPLogin soapLogin = getBean(SOAPLoginDao.class).get(secureHash.toString());
+					roomId = soapLogin.getRoomId();
+					pp = pp.mergeWith(SwfPanel.addServer(roomId, false));
+				}
+				//TODO access denied
+			} else if (!invitationHash.isEmpty()) {
+				roomId = getBean(InvitationDao.class).getInvitationByHashCode(invitationHash.toString(), true).getRoom().getId();
+			}
+		} catch (Exception e) {
+			//no-op
+		}
+		StringValue swfVal = pp.get("swf");
+		PageParameters spp = new PageParameters(pp);
+		if (roomId != null) {
+			spp.mergeWith(new PageParameters().add(WICKET_ROOM_ID, roomId));
+		}
+		String swf = (swfVal.isEmpty() ? getFlashFile() : swfVal.toString())
+				+ new PageParametersEncoder().encodePageParameters(spp);
+		add(new Label("init", String.format("initSwf('%s');", swf)).setEscapeModelStrings(false));
+		add(new AbstractAjaxTimerBehavior(Duration.minutes(5)) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onTimer(AjaxRequestTarget target) {
+				getBean(SessiondataDao.class).checkSession(WebSession.getSid()); //keep SID alive
+			}
+		});
+	}
+
+	private static ResourceReference newResourceReference() {
+		return new JavaScriptResourceReference(SwfPanel.class, "swf-functions.js");
+	}
+	
+	@Override
+	public void renderHead(IHeaderResponse response) {
+		super.renderHead(response);
+		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(newResourceReference())));
+		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forUrl("js/history.js")));
+		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forUrl("js/openmeetings_functions.js")));
+		response.render(new PriorityHeaderItem(CssHeaderItem.forUrl("css/history.css")));
+		//FIXME TODO ugly HACK
+		if (WebSession.get().getClientInfo().getProperties().isBrowserMozillaFirefox()) {
+			response.render(new PriorityHeaderItem(CssHeaderItem.forCSS(".ui-widget-overlay{opacity: 1 !important;}", "ff-veil-hack")));
+		}
+	}
+
+	private String getFlashFile() {
+		return RuntimeConfigurationType.DEVELOPMENT == getApplication().getConfigurationType()
+				? "maindebug.swf11.swf" : "main.swf11.swf";
+	}
+	
+	private static PageParameters addServer(PageParameters pp, Server s) {
+		return pp.add("protocol", s.getProtocol()).add("host", s.getAddress()).add("port", s.getPort()).add("context", s.getWebapp());
+	}
+	
+	public static PageParameters addServer(Long roomId, boolean addBasic) {
+		PageParameters pp = new PageParameters();
+		if (addBasic) {
+			pp.add("wicketsid", getSid()).add(WICKET_ROOM_ID, roomId).add("language", getLanguage());
+		}
+		List<Server> serverList = getBean(ServerDao.class).getActiveServers();
+
+		long minimum = -1;
+		Server result = null;
+		HashMap<Server, List<Long>> activeRoomsMap = new HashMap<Server, List<Long>>();
+		for (Server server : serverList) {
+			List<Long> roomIds = getBean(SessionManager.class).getActiveRoomIdsByServer(server);
+			if (roomIds.contains(roomId)) {
+				// if the room is already opened on a server, redirect the user to that one,
+				log.debug("Room is already opened on a server " + server.getAddress());
+				return addServer(pp, server);
+			}
+			activeRoomsMap.put(server, roomIds);
+		}
+		for (Map.Entry<Server, List<Long>> entry : activeRoomsMap.entrySet()) {
+			List<Long> roomIds = entry.getValue();
+			long capacity = getBean(RoomDao.class).getRoomsCapacityByIds(roomIds);
+			if (minimum < 0 || capacity < minimum) {
+				minimum = capacity;
+				result = entry.getKey();
+			}
+			log.debug("Checking server: " + entry.getKey() + " Number of rooms " + roomIds.size() + " RoomIds: "
+					+ roomIds + " max(Sum): " + capacity);
+		}
+		return result == null ? pp : addServer(pp, result);
+	}
+}

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.html
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.html?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.html (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.html Thu Apr 14 11:46:52 2016
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+      http://www.apache.org/licenses/LICENSE-2.0
+    	  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  
+-->
+<html xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+	<div class="ui-widget-header">
+		<div onclick="toggleActivities();" class="clickable control block ui-widget-header ui-state-active"><div class="ui-icon ui-icon-carat-1-n sort-icon"></div><div class="label"><wicket:message key="1363"/></div></div>
+	</div>
+	<div wicket:id="container" class="area ui-widget-content">
+		<div wicket:id="activities" class="activity item ui-helper-clearfix ui-corner-all">
+			<span wicket:id="close" class="ui-icon ui-icon-close ui-corner-all align-right clickable" wicket:message="title:85"></span>
+			<div wicket:id="text"></div>
+		</div>
+	</div>
+</wicket:panel>
+</html>

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.java
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.java?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.java (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/ActivitiesPanel.java Thu Apr 14 11:46:52 2016
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web.room.activities;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+import static org.apache.openmeetings.web.app.Application.getBean;
+import static org.apache.openmeetings.web.app.WebSession.getUserId;
+import static org.apache.openmeetings.web.room.RoomPanel.isModerator;
+import static org.apache.openmeetings.web.util.CallbackFunctionHelper.getNamedFunction;
+import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.openmeetings.db.dao.user.UserDao;
+import org.apache.openmeetings.db.entity.user.User;
+import org.apache.openmeetings.web.common.BasePanel;
+import org.apache.openmeetings.web.room.activities.Activity.Type;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.PriorityHeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+public class ActivitiesPanel extends BasePanel {
+	private static final long serialVersionUID = 1L;
+	private static final Logger log = Red5LoggerFactory.getLogger(ActivitiesPanel.class, webAppRootKey);
+	private static final String PARAM_UID = "uid";
+	private static final String ACTION = "action";
+	private static final String PARAM_ROOM_ID = "roomid";
+	private enum Action {
+		accept, decline, close
+	};
+	private static ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
+		@Override
+		protected DateFormat initialValue() {
+			return new SimpleDateFormat("HH:mm:ss");
+		};
+	};
+	private final Map<String, Activity> activities = new LinkedHashMap<>();
+	private final long roomId;
+	private final WebMarkupContainer container = new WebMarkupContainer("container");
+	private final AbstractDefaultAjaxBehavior action = new AbstractDefaultAjaxBehavior() {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		protected void respond(AjaxRequestTarget target) {
+			try {
+				String uid = getRequest().getRequestParameters().getParameterValue(PARAM_UID).toString(); 
+				long roomId = getRequest().getRequestParameters().getParameterValue(PARAM_ROOM_ID).toLong();
+				assert(ActivitiesPanel.this.roomId == roomId);
+				Action action = Action.valueOf(getRequest().getRequestParameters().getParameterValue(ACTION).toString());
+				Activity a = activities.get(uid);
+				if (a != null) {
+					if (action == Action.close && (a.getType() == Type.roomEnter || a.getType() == Type.roomExit)) {
+						activities.remove(uid);
+						update(target);
+					} else if (isModerator(getUserId(), roomId)) {
+						switch (a.getType()) {
+							case requestRightModerator:
+								break;
+							default:
+								break;	
+						}
+					}
+				} else {
+					log.error("It seems like we are being hacked!!!!");
+				}
+			} catch (Exception e) {
+				log.error("Unexpected exception while processing activity action", e);
+			}
+		}
+		
+		@Override
+		public void renderHead(Component component, IHeaderResponse response) {
+			super.renderHead(component, response);
+			response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forScript(getNamedFunction("activityAction", this, explicit(PARAM_ROOM_ID), explicit(ACTION), explicit(PARAM_UID)), "activityAction")));
+		}
+	};
+	private ListView<Activity> lv = new ListView<Activity>("activities", new ArrayList<Activity>()) {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		protected void populateItem(ListItem<Activity> item) {
+			Activity a = item.getModelObject();
+			String text = "";
+			switch (a.getType()) {
+				case roomEnter:
+					text = ""; // TODO should this be fixed?
+					item.setVisible(false);
+					break;
+				case roomExit:
+				{
+					User u = getBean(UserDao.class).get(a.getSender());
+					text = String.format("%s %s %s [%s]", u.getFirstname(), u.getLastname(), getString("1367"), df.get().format(a.getCreated()));
+				}
+					break;
+				case requestRightModerator:
+				{
+					User u = getBean(UserDao.class).get(a.getSender());
+					text = String.format("%s %s %s [%s]", u.getFirstname(), u.getLastname(), getString("room.action.request.right.moderator"), df.get().format(a.getCreated()));
+					//FIXME TODO actions
+				}
+				//ask question 693
+					break;
+			}
+			item.add(new WebMarkupContainer("close").add(new AttributeAppender("onclick", String.format("activityAction(%s, '%s', '%s');", roomId, Action.close.name(), a.getUid()))));
+			item.add(new Label("text", text));
+			item.add(AttributeAppender.append("class", getClass(a)));
+		}
+		
+		private String getClass(Activity a) {
+			switch (a.getType()) {
+				case requestRightModerator:
+					return "ui-state-highlight";
+				case roomEnter:
+				case roomExit:
+			}
+			return "ui-state-default";
+		}
+	};
+
+	public void addActivity(String uid, Long userId, Activity.Type type, IPartialPageRequestHandler handler) {
+		//if (getUserId() != userId) {//FIXME should be replaced with client-id
+			activities.put(uid, new Activity(uid, userId,  type));
+			update(handler);
+		//}
+	}
+
+	public void update(IPartialPageRequestHandler handler) {
+		lv.setList(new ArrayList<>(activities.values()));
+		handler.add(container);
+	}
+	
+	public ActivitiesPanel(String id, long roomId) {
+		super(id);
+		this.roomId = roomId;
+		setOutputMarkupPlaceholderTag(true);
+		setMarkupId(id);
+		add(container.add(lv).setOutputMarkupId(true));
+		add(action);
+	}
+	
+	@Override
+	public void renderHead(IHeaderResponse response) {
+		super.renderHead(response);
+		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(ActivitiesPanel.class, "activities.js"))));
+		response.render(CssHeaderItem.forUrl("css/activities.css"));
+	}
+}

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/Activity.java
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/Activity.java?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/Activity.java (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/Activity.java Thu Apr 14 11:46:52 2016
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web.room.activities;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Activity implements Serializable {
+	private static final long serialVersionUID = 1L;
+	public enum Type {
+		roomEnter
+		, roomExit
+		, requestRightModerator
+	}
+	private final String uid;
+	private final Long sender;
+	private final Date created;
+	private final Type type;
+	
+	public Activity(String uid, Long sender, Type type) {
+		this.uid = uid;
+		this.sender = sender;
+		this.type = type;
+		this.created = new Date();
+	}
+
+	public String getUid() {
+		return uid;
+	}
+
+	public Long getSender() {
+		return sender;
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+	public Date getCreated() {
+		return created;
+	}
+}

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/activities.js
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/activities.js?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/activities.js (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/activities/activities.js Thu Apr 14 11:46:52 2016
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+var closedHeight = "20px", openedHeight = "345px";
+function openActivities() {
+	var activities = $('#activitiesPanel');
+	if (activities.height() < 24) {
+		$('.control.block .ui-icon', activities).removeClass('ui-icon-carat-1-n').addClass('ui-icon-carat-1-s');
+		activities.animate({height: openedHeight}, 1000);
+	}
+}
+function closeActivities() {
+	var activities = $('#activitiesPanel');
+	if (activities.height() > 24) {
+		$('.control.block .ui-icon', activities).removeClass('ui-icon-carat-1-s').addClass('ui-icon-carat-1-n');
+		activities.animate({height: closedHeight}, 1000);
+	}
+}
+function toggleActivities() {
+	if ($('#activitiesPanel').height() < 24) {
+		openActivities();
+	} else {
+		closeActivities();
+	}
+}

Copied: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/InvitationDialog.java (from r1739057, openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/InvitationDialog.java)
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/InvitationDialog.java?p2=openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/InvitationDialog.java&p1=openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/InvitationDialog.java&r1=1739057&r2=1739063&rev=1739063&view=diff
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/InvitationDialog.java (original)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/InvitationDialog.java Thu Apr 14 11:46:52 2016
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.openmeetings.web.room;
+package org.apache.openmeetings.web.room.menu;
 
 import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
 import static org.apache.openmeetings.web.app.Application.getBean;

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.html
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.html?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.html (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.html Thu Apr 14 11:46:52 2016
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+      http://www.apache.org/licenses/LICENSE-2.0
+    	  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  
+-->
+<html xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+	<div wicket:id="roomMenu"></div>
+	<div class="room menu right">
+		<span wicket:id="ask" class="icon ask"></span>
+		<span wicket:id="share" class="icon share"></span>
+		<span wicket:id="recording" class="room recording"></span>
+		<span wicket:id="roomName" class="room name"></span>
+	</div>
+	<div wicket:id="invite"></div>
+	<div wicket:id="createPoll"></div>
+	<div wicket:id="vote"></div>
+	<div wicket:id="pollResults"></div>
+</wicket:panel>
+</html>

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java Thu Apr 14 11:46:52 2016
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web.room.menu;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_APPLICATION_BASE_URL;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_REDIRECT_URL_FOR_EXTERNAL_KEY;
+import static org.apache.openmeetings.web.app.Application.getBean;
+import static org.apache.openmeetings.web.app.Application.removeUserFromRoom;
+import static org.apache.openmeetings.web.app.WebSession.getUserId;
+import static org.apache.openmeetings.web.util.OmUrlFragment.ROOMS_PUBLIC;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
+import org.apache.openmeetings.db.dao.room.PollDao;
+import org.apache.openmeetings.db.dao.user.UserDao;
+import org.apache.openmeetings.db.entity.room.Room;
+import org.apache.openmeetings.db.entity.user.User;
+import org.apache.openmeetings.db.entity.user.User.Right;
+import org.apache.openmeetings.web.app.Application;
+import org.apache.openmeetings.web.app.Client;
+import org.apache.openmeetings.web.app.WebSession;
+import org.apache.openmeetings.web.common.OmButton;
+import org.apache.openmeetings.web.common.menu.MenuPanel;
+import org.apache.openmeetings.web.common.menu.RoomMenuItem;
+import org.apache.openmeetings.web.room.RoomPanel;
+import org.apache.openmeetings.web.room.message.RoomMessage;
+import org.apache.openmeetings.web.room.poll.CreatePollDialog;
+import org.apache.openmeetings.web.room.poll.PollResultsDialog;
+import org.apache.openmeetings.web.room.poll.VoteDialog;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.request.flow.RedirectToUrlException;
+import org.apache.wicket.util.string.Strings;
+
+import com.googlecode.wicket.jquery.ui.widget.menu.IMenuItem;
+
+public class RoomMenuPanel extends Panel {
+	private static final long serialVersionUID = 1L;
+	private final InvitationDialog invite;
+	private final CreatePollDialog createPoll;
+	private final VoteDialog vote;
+	private final PollResultsDialog pollResults;
+	private final MenuPanel menuPanel;
+	private final StartSharingButton shareBtn;
+	private final OmButton askBtn = new OmButton("ask") {
+		private static final long serialVersionUID = 1L;
+		{
+			setOutputMarkupPlaceholderTag(true);
+			setVisible(false);
+		}
+		@Override
+		protected void onClick(AjaxRequestTarget target) {
+			RoomPanel.broadcast(new RoomMessage(room.getRoom().getId(), RoomMessage.Type.requestRightModerator));
+		}
+	};
+	private final RoomPanel room;
+	private final RoomMenuItem exitMenuItem = new RoomMenuItem(Application.getString(308), Application.getString(309), "room menu exit") {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public void onClick(AjaxRequestTarget target) {
+			exit(target);
+		}
+	};
+	private final RoomMenuItem filesMenu = new RoomMenuItem(Application.getString(245), null, false);
+	private final RoomMenuItem actionsMenu = new RoomMenuItem(Application.getString(635), null, false);
+	private final RoomMenuItem inviteMenuItem = new RoomMenuItem(Application.getString(213), Application.getString(1489), false) {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public void onClick(AjaxRequestTarget target) {
+			invite.updateModel(target);
+			invite.open(target);
+		}
+	};
+	private final RoomMenuItem shareMenuItem = new RoomMenuItem(Application.getString(239), Application.getString(1480), false) {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public void onClick(AjaxRequestTarget target) {
+			shareBtn.onClick(target);
+		}
+	};
+	private final RoomMenuItem applyModerMenuItem = new RoomMenuItem(Application.getString(784), Application.getString(1481), false);
+	private final RoomMenuItem applyWbMenuItem = new RoomMenuItem(Application.getString(785), Application.getString(1492), false);
+	private final RoomMenuItem applyAvMenuItem = new RoomMenuItem(Application.getString(786), Application.getString(1482), false);
+	private final RoomMenuItem pollCreateMenuItem = new RoomMenuItem(Application.getString(24), Application.getString(1483), false) {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public void onClick(AjaxRequestTarget target) {
+			createPoll.updateModel(target, null); //TODO FIXME
+			createPoll.open(target);
+		}
+	};
+	private final RoomMenuItem pollVoteMenuItem = new RoomMenuItem(Application.getString(42), Application.getString(1485), false) {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public void onClick(AjaxRequestTarget target) {
+			vote.updateModel(target);
+			vote.open(target);
+		}
+	};
+	private final RoomMenuItem pollResultMenuItem = new RoomMenuItem(Application.getString(37), Application.getString(1484), false) {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public void onClick(AjaxRequestTarget target) {
+			pollResults.updateModel(target, room.getClient().hasRight(Client.Right.moderator));
+			pollResults.open(target);
+		}
+	};
+	private final RoomMenuItem sipDialerMenuItem = new RoomMenuItem(Application.getString(1447), Application.getString(1488), false);
+
+	public RoomMenuPanel(String id, final RoomPanel room) {
+		super(id);
+		this.room = room;
+		Room r = room.getRoom();
+		add((menuPanel = new MenuPanel("roomMenu", getMenu())).setVisible(!r.getHideTopBar()));
+		add(askBtn);
+		add(new Label("roomName", r.getName()));
+		add(new Label("recording", "Recording started").setVisible(false)); //FIXME add/remove
+		add(shareBtn = new StartSharingButton("share", room.getClient()));
+		add(invite = new InvitationDialog("invite", room.getRoom().getId()));
+		add(createPoll = new CreatePollDialog("createPoll", room.getRoom().getId()));
+		add(vote = new VoteDialog("vote", room.getRoom().getId()));
+		add(pollResults = new PollResultsDialog("pollResults", room.getRoom().getId()));
+	}
+	
+	@Override
+	protected void onInitialize() {
+		super.onInitialize();
+		askBtn.add(new AttributeAppender("title", getString("906")));
+	}
+	
+	private List<IMenuItem> getMenu() {
+		List<IMenuItem> menu = new ArrayList<>();
+		exitMenuItem.setEnabled(false);
+		exitMenuItem.setTop(true);
+		menu.add(exitMenuItem);
+		
+		filesMenu.getItems().add(new RoomMenuItem(Application.getString(15), Application.getString(1479)));
+		filesMenu.setTop(true);
+		menu.add(filesMenu);
+		
+		actionsMenu.setTop(true);
+		actionsMenu.getItems().add(inviteMenuItem);
+		actionsMenu.getItems().add(shareMenuItem); //FIXME enable/disable
+		actionsMenu.getItems().add(applyModerMenuItem); //FIXME enable/disable
+		actionsMenu.getItems().add(applyWbMenuItem); //FIXME enable/disable
+		actionsMenu.getItems().add(applyAvMenuItem); //FIXME enable/disable
+		actionsMenu.getItems().add(pollCreateMenuItem);
+		actionsMenu.getItems().add(pollResultMenuItem); //FIXME enable/disable
+		actionsMenu.getItems().add(pollVoteMenuItem); //FIXME enable/disable
+		actionsMenu.getItems().add(sipDialerMenuItem);
+		actionsMenu.getItems().add(new RoomMenuItem(Application.getString(1126), Application.getString(1490)));
+		menu.add(actionsMenu);
+		return menu;
+	}
+	
+	public void update(IPartialPageRequestHandler handler) {
+		boolean pollExists = getBean(PollDao.class).hasPoll(room.getRoom().getId());
+		User u = getBean(UserDao.class).get(getUserId());
+		boolean notExternalUser = u.getType() != User.Type.external && u.getType() != User.Type.contact;
+		exitMenuItem.setEnabled(notExternalUser);//TODO check this
+		filesMenu.setEnabled(room.getSidebar().isShowFiles());
+		actionsMenu.setEnabled(!room.getRoom().getHideActionsMenu());
+		boolean moder = room.getClient().hasRight(Client.Right.moderator);
+		inviteMenuItem.setEnabled(notExternalUser && moder);
+		//TODO add check "sharing started"
+		Room r = room.getRoom();
+		boolean shareVisible = Room.Type.interview != r.getType() && !r.getHideScreenSharing() && moder;
+		shareMenuItem.setEnabled(shareVisible);
+		shareBtn.setVisible(shareMenuItem.isEnabled());
+		//FIXME TODO apply* should be enabled if moder is in room
+		applyModerMenuItem.setEnabled(!moder);
+		applyWbMenuItem.setEnabled(!moder);
+		applyAvMenuItem.setEnabled(!moder);
+		pollCreateMenuItem.setEnabled(moder);
+		pollVoteMenuItem.setEnabled(pollExists && notExternalUser && !getBean(PollDao.class).hasVoted(r.getId(), getUserId()));
+		pollResultMenuItem.setEnabled(pollExists || getBean(PollDao.class).getArchived(r.getId()).size() > 0);
+		//TODO sip menus
+		menuPanel.update(handler);
+		//FIXME TODO add ask question button
+		//FIXME TODO askBtn should be visible if moder is in room
+		handler.add(askBtn.setVisible(!moder), shareBtn.setVisible(shareVisible));
+	}
+
+	public void pollCreated(IPartialPageRequestHandler handler) {
+		vote.updateModel(handler);
+		vote.open(handler);
+	}
+	
+	public void exit(IPartialPageRequestHandler handler) {
+		if (WebSession.getRights().contains(Right.Dashboard)) {
+			room.getMainPage().updateContents(ROOMS_PUBLIC, handler);
+			roomExit(room, false);
+		} else {
+			String url = getBean(ConfigurationDao.class).getConfValue(CONFIG_REDIRECT_URL_FOR_EXTERNAL_KEY, String.class, "");
+			if (Strings.isEmpty(url)) {
+				url = getBean(ConfigurationDao.class).getConfValue(CONFIG_APPLICATION_BASE_URL, String.class, "");
+			}
+			throw new RedirectToUrlException(url);
+		}
+	}
+
+	public static void roomExit(RoomPanel room) {
+		roomExit(room, true);
+	}
+	
+	public static void roomExit(RoomPanel room, boolean broadcast) {
+		Client c = room.getClient();
+		removeUserFromRoom(c);
+		if (broadcast) {
+			RoomMessage m = new RoomMessage(c.getRoomId(), c.getUserId(), RoomMessage.Type.roomExit);
+			RoomPanel.broadcast(m);
+		}
+	}
+}

Copied: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/StartSharingButton.java (from r1739057, openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/StartSharingEventBehavior.java)
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/StartSharingButton.java?p2=openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/StartSharingButton.java&p1=openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/StartSharingEventBehavior.java&r1=1739057&r2=1739063&rev=1739063&view=diff
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/StartSharingEventBehavior.java (original)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/StartSharingButton.java Thu Apr 14 11:46:52 2016
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.openmeetings.web.room;
+package org.apache.openmeetings.web.room.menu;
 
 import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_SCREENSHARING_ALLOW_REMOTE;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_SCREENSHARING_FPS;
@@ -26,9 +26,6 @@ import static org.apache.openmeetings.ut
 import static org.apache.openmeetings.web.app.Application.getBean;
 import static org.apache.openmeetings.web.app.WebSession.getLanguage;
 import static org.apache.openmeetings.web.room.RoomBroadcaster.getClient;
-import static org.apache.openmeetings.web.room.RoomPanel.PARAM_PUBLIC_SID;
-import static org.apache.openmeetings.web.room.RoomPanel.PARAM_URL;
-import static org.apache.openmeetings.web.util.CallbackFunctionHelper.getParam;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -46,21 +43,23 @@ import org.apache.openmeetings.db.dao.ro
 import org.apache.openmeetings.db.entity.room.Client;
 import org.apache.openmeetings.db.entity.room.Room;
 import org.apache.openmeetings.util.OmFileHelper;
+import org.apache.openmeetings.web.app.Application;
+import org.apache.openmeetings.web.common.OmButton;
 import org.apache.openmeetings.web.util.AjaxDownload;
-import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.behavior.AttributeAppender;
 import org.apache.wicket.util.resource.StringResourceStream;
 import org.apache.wicket.util.string.Strings;
 import org.red5.logging.Red5LoggerFactory;
 import org.slf4j.Logger;
 
-public class StartSharingEventBehavior extends AbstractDefaultAjaxBehavior {
+public class StartSharingButton extends OmButton {
 	private static final long serialVersionUID = 1L;
-	private static final Logger log = Red5LoggerFactory.getLogger(StartSharingEventBehavior.class, webAppRootKey);
+	private static final Logger log = Red5LoggerFactory.getLogger(StartSharingButton.class, webAppRootKey);
 	private static final String CDATA_BEGIN = "<![CDATA[";
 	private static final String CDATA_END = "]]>";
 	private final AjaxDownload download;
-	private final Long roomId;
+	private final org.apache.openmeetings.web.app.Client c;
 	private enum Protocol {
 		rtmp
 		, rtmpe
@@ -68,41 +67,40 @@ public class StartSharingEventBehavior e
 		, rtmpt
 	}
 
-	public StartSharingEventBehavior(Long _roomId) {
-		this.roomId = _roomId;
-		download = new AjaxDownload(true) {
+	public StartSharingButton(String id, org.apache.openmeetings.web.app.Client c) {
+		super(id);
+		this.c = c;
+		setOutputMarkupPlaceholderTag(true);
+		setVisible(false);
+		add(new AttributeAppender("title", Application.getString(1480)));
+		add(download = new AjaxDownload(true) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
 			protected String getFileName() {
-				return "public_" + roomId + ".jnlp";
+				return String.format("public_%s.jnlp", StartSharingButton.this.c.getRoomId());
 			}
-		};
+		});
 	}
 	
 	@Override
-	protected void onBind() {
-		super.onBind();
-		getComponent().add(download);
-	}
-	
-	@Override
-	protected void respond(AjaxRequestTarget target) {
+	protected void onClick(AjaxRequestTarget target) {
 		//TODO deny download in case other screen sharing is in progress
 		String app = "";
 		try (InputStream jnlp = getClass().getClassLoader().getResourceAsStream("APPLICATION.jnlp")) {
 			ConfigurationDao cfgDao = getBean(ConfigurationDao.class);
 			app = IOUtils.toString(jnlp, StandardCharsets.UTF_8);
 			String baseUrl = cfgDao.getBaseUrl();
-			String _url = getParam(getComponent(), PARAM_URL).toString();
-			URI url = new URI(_url);
-			Room room = getBean(RoomDao.class).get(roomId);
-			String publicSid = getParam(getComponent(), PARAM_PUBLIC_SID).toString();
-			SessionManager sessionManager = getBean(SessionManager.class);
+			String publicSid = c.getUid();
 			Client rc = getClient(publicSid);
 			if (rc == null) {
 				throw new RuntimeException(String.format("Unable to find client by publicSID '%s'", publicSid));
 			}
+			String _url = rc.getTcUrl();
+			URI url = new URI(_url);
+			long roomId = c.getRoomId();
+			Room room = getBean(RoomDao.class).get(roomId);
+			SessionManager sessionManager = getBean(SessionManager.class);
 			String path = url.getPath();
 			path = path.substring(path.lastIndexOf('/') + 1);
 			if (Strings.isEmpty(path) || rc.getRoomId() == null || !path.equals(rc.getRoomId().toString()) || !rc.getRoomId().equals(roomId)) {

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/message/RoomMessage.java
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/message/RoomMessage.java?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/message/RoomMessage.java (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/message/RoomMessage.java Thu Apr 14 11:46:52 2016
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web.room.message;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.UUID;
+
+import org.apache.openmeetings.web.app.WebSession;
+import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
+
+public class RoomMessage implements IWebSocketPushMessage, Serializable {
+	private static final long serialVersionUID = 1L;
+	public enum Type {
+		roomEnter
+		, roomExit
+		, pollCreated
+		, pollClosed
+		, pollDeleted
+		, voted
+		, rightUpdated
+		, requestRightModerator
+	}
+	private final Date timestamp;
+	private final String uid;
+	private final Long roomId;
+	private final Long userId;
+	private final Type type;
+
+	public RoomMessage(Long roomId, Type type) {
+		this(roomId, WebSession.getUserId(), type);
+	}
+	
+	public RoomMessage(Long roomId, Long userId, Type type) {
+		this.timestamp = new Date();
+		this.roomId = roomId;
+		this.userId = userId;
+		this.type = type;
+		this.uid = UUID.randomUUID().toString();
+	}
+	
+	public Date getTimestamp() {
+		return timestamp;
+	}
+
+	public Long getRoomId() {
+		return roomId;
+	}
+
+	public Long getUserId() {
+		return userId;
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+	public String getUid() {
+		return uid;
+	}
+}

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room.js
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room.js?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room.js (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room.js Thu Apr 14 11:46:52 2016
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+function setHeight() {
+	var h = $(window).height() - $('#roomMenu').height();
+	$(".room.sidebar.left").height(h);
+	var p = $(".room.sidebar.left .tabs");
+	p.height(h - 5); //FIXME hacks
+	$(".user.list", p).height(h - $("ul", p).height() - 15); //FIXME hacks
+	$(".room.wb.area").height(h);
+	$(".room.wb.area .wb").height(h);
+}
+
+$(document).ready(function() {
+	$(window).on('resize.openmeetings', function() {
+		roomWidth = $(window).width();
+		setHeight();
+	});
+});
+
+var roomWidth = $(window).width();
+function roomLoad() {
+	$(".room.sidebar.left").resizable({
+		handles: "e"
+		, stop: function(event, ui) {
+			//TODO not really works, need to be investigated
+			var w = roomWidth - $(this).width() - 5;
+			$(".room.wb.area").width(w);
+			$(".room.wb.area .wb").width(w);
+		}
+	});
+	setHeight();
+}
+function startPrivateChat(el) {
+	addChatTab('chatTab-u' + el.parent().parent().data("userid"), el.parent().parent().find('.user.name').text());
+	openChat();
+	$('#chatMessage .wysiwyg-editor').click();
+}

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java Thu Apr 14 11:46:52 2016
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.web.room.sidebar;
+
+import static org.apache.openmeetings.web.app.Application.getBean;
+import static org.apache.openmeetings.web.app.WebSession.getUserId;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.openmeetings.db.dao.file.FileExplorerItemDao;
+import org.apache.openmeetings.db.dao.user.UserDao;
+import org.apache.openmeetings.db.entity.file.FileExplorerItem;
+import org.apache.openmeetings.db.entity.file.FileItem;
+import org.apache.openmeetings.db.entity.file.FileItem.Type;
+import org.apache.openmeetings.db.entity.record.Recording;
+import org.apache.openmeetings.db.entity.user.Group;
+import org.apache.openmeetings.db.entity.user.GroupUser;
+import org.apache.openmeetings.web.app.Application;
+import org.apache.openmeetings.web.common.tree.FileItemTree;
+import org.apache.openmeetings.web.common.tree.FileTreePanel;
+import org.apache.openmeetings.web.common.tree.MyRecordingTreeProvider;
+import org.apache.openmeetings.web.common.tree.PublicRecordingTreeProvider;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+public class RoomFilePanel extends FileTreePanel {
+	private static final long serialVersionUID = 1L;
+	private final long roomId;
+
+	public RoomFilePanel(String id, final long roomId) {
+		super(id);
+		this.roomId = roomId;
+	}
+	
+	@Override
+	public void updateSizes() {
+		// TODO Auto-generated method stub
+		
+	}
+	
+	@Override
+	public void update(AjaxRequestTarget target, FileItem f) {
+		// TODO Auto-generated method stub
+		
+	}
+	
+	@Override
+	protected Component getUpload(String id) {
+		Component u = super.getUpload(id);
+		u.setVisible(true);
+		return u;
+	}
+	
+	@Override
+	public void defineTrees() {
+		FileExplorerItem f = new FileExplorerItem();
+		f.setOwnerId(getUserId());
+		selectedFile.setObject(f);
+		treesView.add(selected = new FileItemTree<FileExplorerItem>(treesView.newChildId(), this, new FilesTreeProvider(null)));
+		treesView.add(new FileItemTree<FileExplorerItem>(treesView.newChildId(), this, new FilesTreeProvider(roomId)));
+		treesView.add(new FileItemTree<Recording>(treesView.newChildId(), this, new MyRecordingTreeProvider()));
+		treesView.add(new FileItemTree<Recording>(treesView.newChildId(), this, new PublicRecordingTreeProvider(null, null)));
+		for (GroupUser ou : getBean(UserDao.class).get(getUserId()).getGroupUsers()) {
+			Group o = ou.getGroup();
+			treesView.add(new FileItemTree<Recording>(treesView.newChildId(), this, new PublicRecordingTreeProvider(o.getId(), o.getName())));
+		}
+	}
+	
+	@Override
+	public void createFolder(String name) {
+		if (selectedFile.getObject() instanceof Recording) {
+			createRecordingFolder(name);
+		} else {
+			FileExplorerItem f = new FileExplorerItem();
+			f.setName(name);
+			f.setInsertedBy(getUserId());
+			f.setInserted(new Date());
+			f.setType(Type.Folder);;
+			FileItem p = selectedFile.getObject();
+			long parentId = p.getId();
+			f.setParentId(Type.Folder == p.getType() && parentId > 0 ? parentId : null);
+			f.setOwnerId(p.getOwnerId());
+			f.setRoomId(p.getRoomId());
+			getBean(FileExplorerItemDao.class).update(f);
+		}
+	}
+
+	static class FilesTreeProvider implements ITreeProvider<FileExplorerItem> {
+		private static final long serialVersionUID = 1L;
+		Long roomId = null;
+
+		FilesTreeProvider(Long roomId) {
+			this.roomId = roomId;
+		}
+		
+		@Override
+		public void detach() {
+			// TODO LDM should be used
+		}
+
+		@Override
+		public boolean hasChildren(FileExplorerItem node) {
+			return node.getId() <= 0 || Type.Folder == node.getType();
+		}
+
+		@Override
+		public Iterator<? extends FileExplorerItem> getChildren(FileExplorerItem node) {
+			FileExplorerItemDao dao = getBean(FileExplorerItemDao.class);
+			List<FileExplorerItem> list = null;
+			if (node.getId() == 0) {
+				list = dao.getByOwner(node.getOwnerId());
+			} else if (node.getId() < 0) {
+				list = dao.getByRoom(roomId);
+			} else {
+				list = dao.getByParent(node.getId());
+			}
+			return list.iterator();
+		}
+
+		@Override
+		public IModel<FileExplorerItem> model(FileExplorerItem object) {
+			// TODO LDM should be used
+			return Model.of(object);
+		}
+
+		@Override
+		public Iterator<? extends FileExplorerItem> getRoots() {
+			FileExplorerItem f = new FileExplorerItem();
+			f.setRoomId(roomId);
+			f.setType(Type.Folder);
+			if (roomId == null) {
+				f.setId(0L);
+				f.setOwnerId(getUserId());
+				f.setName(Application.getString(706));
+			} else {
+				f.setId(-roomId);
+				f.setName(Application.getString(707));
+			}
+			return Arrays.asList(f).iterator();
+		}
+	}
+}

Added: openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html
URL: http://svn.apache.org/viewvc/openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html?rev=1739063&view=auto
==============================================================================
--- openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html (added)
+++ openmeetings/application/branches/3.2.x/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html Thu Apr 14 11:46:52 2016
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+      http://www.apache.org/licenses/LICENSE-2.0
+    	  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  
+-->
+<html xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+	<div class="tabs" wicket:id="tabs"></div>
+	
+	<wicket:fragment wicket:id="user-panel">
+		<div class="user list">
+			<div wicket:id="user" class="user ui-corner-all ui-widget-content">
+				<div wicket:id="name" class="user name"></div>
+				<div class="user actions">
+					<span wicket:id="privateChat" class="private-chat om-icon align-right clickable" wicket:message="title:1493" onclick="startPrivateChat($(this));"></span>
+					<div class="clear"></div>
+				</div>
+			</div>
+		</div>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="file-panel">
+		<div class="file list" wicket:id="tree"></div>
+	</wicket:fragment>
+</wicket:panel>
+</html>