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 2020/02/09 11:31:39 UTC

[openmeetings] branch csp updated: [OPENMEETINGS-2165] more work on confirmations and admin

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch csp
in repository https://gitbox.apache.org/repos/asf/openmeetings.git


The following commit(s) were added to refs/heads/csp by this push:
     new 7ab8f03  [OPENMEETINGS-2165] more work on confirmations and admin
7ab8f03 is described below

commit 7ab8f03c61bd14381b50b5c26e0f42de82a4e3fe
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Sun Feb 9 18:31:24 2020 +0700

    [OPENMEETINGS-2165] more work on confirmations and admin
---
 .../openmeetings/web/admin/backup/BackupPanel.html |  19 +-
 .../openmeetings/web/admin/backup/BackupPanel.java |   6 +-
 .../web/admin/configurations/ConfigForm.java       |  35 +-
 .../web/admin/configurations/ConfigsPanel.html     |  61 +--
 .../web/admin/configurations/admin-config.js       |  32 ++
 .../web/admin/connection/ConnectionsPanel.java     |   2 +-
 .../openmeetings/web/admin/email/EmailForm.java    |   2 +-
 .../web/admin/groups/GroupUsersPanel.java          |   2 +-
 .../web/admin/labels/AddLanguageDialog.html        |   4 +-
 .../web/admin/labels/AddLanguageDialog.java        |  89 ++-
 .../openmeetings/web/admin/labels/LangPanel.html   |  49 +-
 .../openmeetings/web/admin/labels/LangPanel.java   |  39 +-
 .../openmeetings/web/admin/oauth/OAuthForm.java    |   2 +-
 .../openmeetings/web/admin/rooms/RoomForm.java     |   2 +-
 .../openmeetings/web/common/FormActionsPanel.java  |   1 +
 .../web/common/PagedEntityListPanel.html           |   8 +-
 .../web/common/PagedEntityListPanel.java           |  19 +-
 .../web/common/PagingNavigatorPanel.html           |   8 +-
 .../web/common/PagingNavigatorPanel.java           |  18 +-
 .../web/common/UploadableImagePanel.html           |  19 +-
 .../{ => confirmation}/ConfirmableAjaxBorder.html  |   0
 .../{ => confirmation}/ConfirmableAjaxBorder.java  |   2 +-
 .../common/confirmation/ConfirmationBehavior.java  |  84 +++
 .../common/confirmation/ConfirmationConfig.java    | 116 ++++
 .../common/confirmation/bootstrap-confirmation.js  | 597 +++++++++++++++++++++
 .../web/common/tree/FileTreePanel.java             |   4 +-
 .../web/room/sidebar/RoomFilePanel.java            |   2 +-
 .../openmeetings/web/room/sidebar/RoomSidebar.java |   4 +-
 .../openmeetings/web/user/chat/ChatToolbar.java    |   2 +-
 .../web/user/profile/EditProfileForm.html          |  53 +-
 .../web/user/profile/EditProfilePanel.html         |   2 +-
 .../web/user/profile/InvitationDetails.java        |   2 +-
 .../web/user/profile/MessagesContactsPanel.java    |   2 +-
 .../web/util/CallbackFunctionHelper.java           |   5 +-
 openmeetings-web/src/main/webapp/css/raw-admin.css |   3 -
 .../src/main/webapp/css/raw-general.css            |   2 +-
 36 files changed, 1066 insertions(+), 231 deletions(-)

diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.html
index e4b7f44..95cfa17 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.html
@@ -22,14 +22,17 @@
 <html xmlns:wicket="http://wicket.apache.org">
 <wicket:panel>
 	<div wicket:id="feedback"></div>
-	<form wicket:id="backupUpload" class="adminForm adminBackupForm">
+	<form wicket:id="backupUpload" class="adminForm adminBackupForm container">
 		<fieldset class="ui-widget-content">
 			<legend class="ui-widget-header"><wicket:message key="1066" /></legend>
 			<div class="formelement">
 				<div><i class="fas fa-info-circle m-1"></i><wicket:message key="1065" /></div>
 			</div>
 			<div class="formelement">
-				<label wicket:for="includeFilesInBackup"><wicket:message key="1537" /></label> <input type="checkbox" wicket:id="includeFilesInBackup" />
+				<div class="custom-control custom-checkbox m-2">
+					<label class="custom-control-label" wicket:for="includeFilesInBackup"><wicket:message key="1537" /></label>
+					<input class="custom-control-input" type="checkbox" wicket:id="includeFilesInBackup" />
+				</div>
 			</div>
 			<div class="formelement">
 				<span wicket:id="progress"></span>
@@ -37,14 +40,14 @@
 			</div>
 			<div class="formelement">
 				<!-- Perform Download -->
-				<div wicket:id="download" class="btn"><wicket:message key="1066"/></div>
+				<button wicket:id="download"></button>
 				<!-- Perform Upload -->
-				<div class="fileinput fileinput-new d-inline m-0" data-provides="fileinput">
-					<span class="btn btn-file ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"><span
-						class="ui-button-text"><wicket:message key="1536"/></span><input class="uploadFileField" wicket:id="fileInput" type="file"/></span>
-				</div>
+				<button class="fileinput fileinput-new d-inline m-0 btn btn-file btn-outline-primary" data-provides="fileinput">
+					<wicket:message key="1536"/>
+					<input class="uploadFileField" wicket:id="fileInput" type="file"/>
+				</button>
 			</div>
-			<div class="formelement">
+			<div class="formelement m-2">
 				<!-- Max upload size -->
 				<wicket:message key="1491" /> <span wicket:id="MaxUploadSize" /><span>MB</span>
 			</div>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.java
index 0971cba..63de964 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/backup/BackupPanel.java
@@ -45,6 +45,7 @@ import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.upload.FileUpload;
 import org.apache.wicket.markup.html.form.upload.FileUploadField;
 import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.request.resource.IResource;
 import org.apache.wicket.resource.FileSystemResource;
 import org.apache.wicket.spring.injection.annot.SpringBean;
@@ -52,9 +53,10 @@ import org.apache.wicket.util.lang.Bytes;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.googlecode.wicket.jquery.ui.form.button.AjaxButton;
 import com.googlecode.wicket.jquery.ui.widget.progressbar.ProgressBar;
 
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxButton;
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
 import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
 /**
  * Panel component to manage Backup Import/Export
@@ -131,7 +133,7 @@ public class BackupPanel extends AdminBasePanel {
 			});
 			add(download);
 			// add an download button
-			add(new AjaxButton("download", this) {
+			add(new BootstrapAjaxButton("download", new ResourceModel("1066"), this, Buttons.Type.Outline_Primary) {
 				private static final long serialVersionUID = 1L;
 
 				@Override
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigForm.java
index 4f35c30..34eb05c 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigForm.java
@@ -29,6 +29,8 @@ import org.apache.openmeetings.web.app.WebSession;
 import org.apache.openmeetings.web.util.DateLabel;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.CheckBox;
@@ -41,6 +43,7 @@ import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 import org.apache.wicket.validation.IValidatable;
 import org.apache.wicket.validation.IValidator;
@@ -55,6 +58,10 @@ import org.apache.wicket.validation.ValidationError;
 public class ConfigForm extends AdminBaseForm<Configuration> {
 	private static final long serialVersionUID = 1L;
 	private final WebMarkupContainer listContainer;
+	private final WebMarkupContainer stringBox = new WebMarkupContainer("string-box");
+	private final WebMarkupContainer numberBox = new WebMarkupContainer("number-box");
+	private final WebMarkupContainer booleanBox = new WebMarkupContainer("boolean-box");
+	private final WebMarkupContainer hotkeyBox = new WebMarkupContainer("hotkey-box");
 	private final TextArea<String> valueS = new TextArea<>("valueS");
 	private final TextField<Long> valueN = new TextField<>("valueN") {
 		private static final long serialVersionUID = 1L;
@@ -81,12 +88,15 @@ public class ConfigForm extends AdminBaseForm<Configuration> {
 
 	private void update(AjaxRequestTarget target) {
 		Configuration c = getModelObject();
-		valueS.setVisible(Type.STRING == c.getType());
-		valueN.setVisible(Type.NUMBER == c.getType());
-		valueB.setVisible(Type.BOOL == c.getType());
-		valueH.setVisible(Type.HOTKEY == c.getType());
+		stringBox.setVisible(Type.STRING == c.getType());
+		numberBox.setVisible(Type.NUMBER == c.getType());
+		booleanBox.setVisible(Type.BOOL == c.getType());
+		hotkeyBox.setVisible(Type.HOTKEY == c.getType());
 		if (target != null) {
-			target.add(valueS, valueN, valueB, valueH);
+			target.add(stringBox, numberBox, booleanBox, hotkeyBox);
+			if (Type.HOTKEY == c.getType()) {
+				target.appendJavaScript("addOmAdminConfigHandlers()");
+			}
 		}
 	}
 
@@ -145,10 +155,11 @@ public class ConfigForm extends AdminBaseForm<Configuration> {
 				}
 			}
 		}));
-		add(valueS.setLabel(new ResourceModel("271")).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
-		add(valueN.setLabel(new ResourceModel("271")).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
-		add(valueB.setLabel(new ResourceModel("271")).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
-		add(valueH.setLabel(new ResourceModel("271")).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
+		stringBox.add(valueS.setLabel(new ResourceModel("271"))).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true);
+		numberBox.add(valueN.setLabel(new ResourceModel("271"))).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true);
+		booleanBox.add(valueB.setLabel(new ResourceModel("271"))).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true);
+		hotkeyBox.add(valueH.setLabel(new ResourceModel("271"))).setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true);
+		add(stringBox, numberBox, booleanBox, hotkeyBox);
 	}
 
 	@Override
@@ -188,4 +199,10 @@ public class ConfigForm extends AdminBaseForm<Configuration> {
 		target.add(listContainer);
 		refresh(target);
 	}
+
+	@Override
+	public void renderHead(IHeaderResponse response) {
+		super.renderHead(response);
+		response.render(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(ConfigForm.class, "admin-config.js")));
+	}
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigsPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigsPanel.html
index 6833e71..8054a67 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigsPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/ConfigsPanel.html
@@ -20,32 +20,6 @@
 -->
 <!DOCTYPE html>
 <html xmlns:wicket="http://wicket.apache.org">
-<wicket:head>
-	<script>
-		function configHandleKey(evt) {
-			const inp = $(evt.target);
-			evt.preventDefault();
-			let val = '';
-			if (evt.ctrlKey) {
-				val += 'Ctrl+';
-			}
-			if (evt.altKey) {
-				val += 'Alt+';
-			}
-			if (evt.shiftKey) {
-				val += 'Shift+';
-			}
-			const code = OmUtil.getKeyCode(evt);
-			if (typeof(code) === 'undefined') {
-				return;
-			}
-			val += code;
-			if (evt.keyCode !== 16 && evt.keyCode !== 17 && evt.keyCode !== 18 && inp.length == 1) {
-				inp.val(val);
-			}
-		}
-	</script>
-</wicket:head>
 <wicket:extend>
 	<div class="adminPanelColumnTable">
 		<div class="adminNav" wicket:id="navigator">[dataview navigator]</div>
@@ -73,22 +47,24 @@
 				<fieldset class="ui-widget-content">
 					<legend class="ui-widget-header"><wicket:message key="266" /></legend>
 					<div class="formelement">
-						<label wicket:for="type"><wicket:message key="45" /></label><select wicket:id="type"></select>
+						<label wicket:for="type" class="col-3 text-right"><wicket:message key="45" /></label>
+						<select class="custom-select col-8" wicket:id="type"></select>
 					</div>
 					<div class="formelement">
-						<label wicket:for="key"><wicket:message key="265" /></label><input type="text" wicket:id="key"/>
+						<label wicket:for="key" class="col-3 text-right"><wicket:message key="265" /></label>
+						<input class="col-8" type="text" wicket:id="key"/>
 					</div>
 					<div class="formelement">
-						<div wicket:enclosure="valueS">
-							<label wicket:for="valueS"><wicket:message key="271" /></label>
-							<textarea wicket:id="valueS"/>
+						<div wicket:id="string-box">
+							<label wicket:for="valueS" class="col-3 text-right"><wicket:message key="271" /></label>
+							<textarea class="col-8" wicket:id="valueS"/>
 						</div>
-						<div wicket:enclosure="valueN">
-							<label wicket:for="valueN"><wicket:message key="271" /></label>
-							<input type="number" wicket:id="valueN"/>
+						<div wicket:id="number-box">
+							<label wicket:for="valueN" class="col-3 text-right"><wicket:message key="271" /></label>
+							<input class="col-8" type="number" wicket:id="valueN"/>
 						</div>
-						<div wicket:enclosure="valueB">
-							<label wicket:for="valueB"><wicket:message key="271" /></label>
+						<div wicket:id="boolean-box">
+							<label wicket:for="valueB" class="col-3 text-right"><wicket:message key="271" /></label>
 							<div class="onoffswitch">
 								<input type="checkbox" class="onoffswitch-checkbox" wicket:id="valueB">
 								<label class="onoffswitch-label" wicket:for="valueB">
@@ -100,19 +76,20 @@
 								</label>
 							</div>
 						</div>
-						<div wicket:enclosure="value">
-							<label wicket:for="value"><wicket:message key="271" /></label>
-							<input type="text" onkeydown="configHandleKey(event)" onpaste="return false;" wicket:id="value"/>
+						<div wicket:id="hotkey-box">
+							<label wicket:for="value" class="col-3 text-right"><wicket:message key="271" /></label>
+							<input type="text" class="hotkey-input" wicket:id="value"/>
 						</div>
 					</div>
 					<div class="formelement">
-						<label><wicket:message key="268" /></label><span wicket:id="updated"/>
+						<label class="col-3 text-right"><wicket:message key="268" /></label><span wicket:id="updated"/>
 					</div>
 					<div class="formelement">
-						<label><wicket:message key="269" /></label><span wicket:id="user.login"/>
+						<label class="col-3 text-right"><wicket:message key="269" /></label><span wicket:id="user.login"/>
 					</div>
 					<div class="formelement">
-						<label wicket:for="comment"><wicket:message key="196" /></label><textarea wicket:id="comment"/>
+						<label class="col-3 text-right" wicket:for="comment"><wicket:message key="196" /></label>
+						<textarea class="col-8" wicket:id="comment"/>
 					</div>
 				</fieldset>
 				<div class="bottom-bumper"></div>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/admin-config.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/admin-config.js
new file mode 100644
index 0000000..edaf85f
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/configurations/admin-config.js
@@ -0,0 +1,32 @@
+/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */
+function addOmAdminConfigHandlers() {
+	$('.hotkey-input')
+		.off()
+		.on('keydown', function(evt) {
+			const inp = $(evt.target);
+			evt.preventDefault();
+			let val = '';
+			if (evt.ctrlKey) {
+				val += 'Ctrl+';
+			}
+			if (evt.altKey) {
+				val += 'Alt+';
+			}
+			if (evt.shiftKey) {
+				val += 'Shift+';
+			}
+			const code = OmUtil.getKeyCode(evt);
+			if (typeof(code) === 'undefined') {
+				return;
+			}
+			val += code;
+			if (evt.keyCode !== 16 && evt.keyCode !== 17 && evt.keyCode !== 18 && inp.length == 1) {
+				inp.val(val);
+			}
+		})
+		.on('paste', function(e) {
+			e.preventDefault();
+			e.stopImmediatePropagation();
+			return false;
+		});
+}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java
index b939d94..b3576ea 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java
@@ -34,6 +34,7 @@ import org.apache.openmeetings.web.admin.AdminBasePanel;
 import org.apache.openmeetings.web.admin.SearchableDataView;
 import org.apache.openmeetings.web.app.ClientManager;
 import org.apache.openmeetings.web.common.PagedEntityListPanel;
+import org.apache.openmeetings.web.common.confirmation.ConfirmationBehavior;
 import org.apache.openmeetings.web.data.SearchableDataProvider;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.ajax.AjaxEventBehavior;
@@ -47,7 +48,6 @@ import org.apache.wicket.spring.injection.annot.SpringBean;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxLink;
 import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
-import de.agilecoders.wicket.extensions.markup.html.bootstrap.confirmation.ConfirmationBehavior;
 
 public class ConnectionsPanel extends AdminBasePanel {
 	private static final long serialVersionUID = 1L;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/email/EmailForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/email/EmailForm.java
index ce8c603..694c54d 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/email/EmailForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/email/EmailForm.java
@@ -20,7 +20,7 @@ package org.apache.openmeetings.web.admin.email;
 
 import org.apache.openmeetings.db.dao.basic.MailMessageDao;
 import org.apache.openmeetings.db.entity.basic.MailMessage;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.util.DateLabel;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.html.WebMarkupContainer;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupUsersPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupUsersPanel.java
index 708ad90..ae62611 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupUsersPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupUsersPanel.java
@@ -29,8 +29,8 @@ import org.apache.openmeetings.db.entity.user.GroupUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.web.admin.SearchableDataView;
 import org.apache.openmeetings.web.app.WebSession;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.common.PagedEntityListPanel;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.data.SearchableDataProvider;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.ajax.AjaxRequestTarget;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.html
index 7ae57de..58ba925 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.html
@@ -20,7 +20,7 @@
 -->
 <!DOCTYPE html>
 <html xmlns:wicket="http://wicket.apache.org">
-<wicket:panel>
+<wicket:extend>
 	<form wicket:id="addLangForm">
 		<div wicket:id="feedback"></div>
 		<table>
@@ -30,5 +30,5 @@
 			</tr>
 		</table>
 	</form>
-</wicket:panel>
+</wicket:extend>
 </html>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.java
index 2651058..c4d2586 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/AddLanguageDialog.java
@@ -18,9 +18,7 @@
  */
 package org.apache.openmeetings.web.admin.labels;
 
-import java.util.Arrays;
 import java.util.IllformedLocaleException;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
@@ -30,26 +28,55 @@ import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.RequiredTextField;
 import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.validation.IValidatable;
 import org.apache.wicket.validation.IValidator;
 import org.apache.wicket.validation.ValidationError;
 
-import com.googlecode.wicket.jquery.ui.widget.dialog.AbstractFormDialog;
-import com.googlecode.wicket.jquery.ui.widget.dialog.DialogButton;
-
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxButton;
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
 import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 
-public class AddLanguageDialog extends AbstractFormDialog<String> {
+public class AddLanguageDialog extends Modal<String> {
 	private static final long serialVersionUID = 1L;
 	private final NotificationPanel feedback = new NotificationPanel("feedback");
-	private DialogButton add;
 	private final Form<Void> form = new Form<>("addLangForm");
 	private final RequiredTextField<String> iso = new RequiredTextField<>("iso", Model.of(""));
 	private final LangPanel langPanel;
 
 	public AddLanguageDialog(String id, final LangPanel langPanel) {
-		super(id, "");
+		super(id);
 		this.langPanel = langPanel;
+	}
+
+	@Override
+	protected void onInitialize() {
+		header(new ResourceModel("362"));
+		setCloseOnEscapeKey(true);
+		setBackdrop(Backdrop.STATIC);
+
+		addButton(new BootstrapAjaxButton("button", new ResourceModel("366"), form, Buttons.Type.Outline_Primary) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSubmit(AjaxRequestTarget target) {
+				try {
+					LabelDao.add(Locale.forLanguageTag(iso.getModelObject()));
+					langPanel.getLangForm().updateLanguages(target);
+					AddLanguageDialog.this.close(target);
+				} catch (Exception e) {
+					error("Failed to add, " + e.getMessage());
+					target.add(feedback);
+				}
+			}
+
+			@Override
+			protected void onError(AjaxRequestTarget target) {
+				target.add(feedback);
+			}
+		});
+
 		add(form.add(feedback.setOutputMarkupId(true), iso.setOutputMarkupId(true)));
 		iso.add(new IValidator<String>() {
 			private static final long serialVersionUID = 1L;
@@ -57,9 +84,9 @@ public class AddLanguageDialog extends AbstractFormDialog<String> {
 			@Override
 			public void validate(IValidatable<String> s) {
 				try {
-					new Locale.Builder().setLanguageTag(s.getValue());
+					new Locale.Builder().setLanguageTag(s.getValue()).build();
 				} catch (IllformedLocaleException e) {
-					s.error(new ValidationError("Invalid code, please use "));
+					s.error(new ValidationError("Invalid code, please specify valid ISO code"));
 					return;
 				}
 				Locale l = Locale.forLanguageTag(s.getValue());
@@ -71,51 +98,13 @@ public class AddLanguageDialog extends AbstractFormDialog<String> {
 				}
 			}
 		});
-	}
-
-	@Override
-	protected void onInitialize() {
-		add = new DialogButton("add", getString("366"));
-		getTitle().setObject(getString("362"));
 		super.onInitialize();
 	}
 
 	@Override
-	public Form<?> getForm() {
-		return form;
-	}
-
-	@Override
-	protected List<DialogButton> getButtons() {
-		return Arrays.asList(add);
-	}
-
-	@Override
-	public DialogButton getSubmitButton() {
-		return add;
-	}
-
-	@Override
-	protected void onOpen(IPartialPageRequestHandler handler) {
+	public Modal<String> show(IPartialPageRequestHandler handler) {
 		iso.setModelObject("");
 		handler.add(iso);
-		super.onOpen(handler);
-	}
-
-	@Override
-	protected void onError(AjaxRequestTarget target, DialogButton btn) {
-		target.add(feedback);
-	}
-
-	@Override
-	protected void onSubmit(AjaxRequestTarget target, DialogButton btn) {
-		try {
-			LabelDao.add(Locale.forLanguageTag(iso.getModelObject()));
-			langPanel.getLangForm().updateLanguages(target);
-			target.appendJavaScript("$('#addLanguage').dialog('close');");
-		} catch (Exception e) {
-			error("Failed to add, " + e.getMessage());
-			target.add(feedback);
-		}
+		return super.show(handler);
 	}
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.html
index 0b4a985..2242030 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.html
@@ -34,32 +34,25 @@
 <wicket:extend>
 	<div wicket:id="feedback"></div>
 	<div class="adminPanelColumnTable label">
-		<div class="lblNav">
-			<div class="ui-button ui-widget ui-corner-all ui-button-icon-only mt-n2" wicket:id="addLangBtn" wicket:message="title:362">
-				<span class="ui-button-icon ui-icon ui-icon-plusthick"></span>&nbsp;
+		<form wicket:id="langForm" class="addLanguagePanel">
+			<div class="input-group input-group-sm">
+				<div class="input-group-prepend">
+					<button class="btn btn-outline-success" wicket:id="addLangBtn" wicket:message="title:362">
+						<i class="fas fa-plus"></i>
+					</button>
+					<button wicket:id="deleteLangBtn" wicket:message="title:363"></button>
+				</div>
+				<select wicket:id="language" class="form-control"></select>
+				<div class="input-group-append">
+					<button wicket:id="export"></button>
+					<button class="fileupload fileupload-new m-0 btn btn-file btn-xs btn-primary" data-provides="fileupload">
+						<wicket:message key="387"/>
+						<input type="file" accept="text/xml" wicket:id="fileInput"/>
+					</button>
+					<span wicket:id="progress">[progressbar]</span>
+				</div>
 			</div>
-			<div class="ui-button ui-widget ui-corner-all ui-button-icon-only ui-state-error mt-n2" wicket:id="deleteLangBtn" wicket:message="title:363">
-				<span class="ui-button-icon ui-icon ui-icon-closethick"></span>&nbsp;
-			</div>
-			<form wicket:id="langForm" class="addLanguagePanel">
-				<table>
-					<tr>
-						<td>
-							<select wicket:id="language"></select>
-						</td>
-						<td>
-							<span wicket:id="export" class="btn"><wicket:message key="360"/></span>
-						</td>
-						<td>
-							<div class="fileupload fileupload-new m-0" data-provides="fileupload">
-								<span class="btn btn-file ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"><span class="ui-button-text"><wicket:message key="387"/></span><input type="file" accept="text/xml" wicket:id="fileInput"/></span>
-							</div>
-							<span wicket:id="progress">[progressbar]</span>
-						</td>
-					</tr>
-				</table>
-			</form>
-		</div>
+		</form>
 		<div class="adminNav" wicket:id="navigator">[dataview navigator]</div>
 		<table class="list-table table-striped table-hover">
 			<thead>
@@ -83,10 +76,12 @@
 				<fieldset class="ui-widget-content">
 					<legend class="ui-widget-header"><wicket:message key="353" /></legend>
 					<div class="formelement">
-						<label wicket:for="key"><wicket:message key="165" /></label><input type="text" wicket:id="key"/>
+						<label wicket:for="key" class="col-3 text-right"><wicket:message key="165" /></label>
+						<input type="text" wicket:id="key" class="col-8"/>
 					</div>
 					<div class="formelement">
-						<label wicket:for="value"><wicket:message key="271" /></label><textarea wicket:id="value"></textarea>
+						<label wicket:for="value" class="col-3 text-right"><wicket:message key="271" /></label>
+						<textarea wicket:id="value" class="col-8"></textarea>
 					</div>
 				</fieldset>
 				<div class="bottom-bumper"></div>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.java
index 1ed3931..1fc8087 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/labels/LangPanel.java
@@ -36,11 +36,11 @@ import org.apache.openmeetings.web.admin.AdminBasePanel;
 import org.apache.openmeetings.web.admin.SearchableDataView;
 import org.apache.openmeetings.web.app.Application;
 import org.apache.openmeetings.web.common.BasePanel;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.common.PagedEntityListPanel;
 import org.apache.openmeetings.web.data.DataViewContainer;
 import org.apache.openmeetings.web.data.OmOrderByBorder;
 import org.apache.openmeetings.web.data.SearchableDataProvider;
+import org.apache.openmeetings.web.util.CallbackFunctionHelper;
 import org.apache.openmeetings.web.util.upload.BootstrapFileUploadBehavior;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.ajax.AjaxEventBehavior;
@@ -55,6 +55,7 @@ import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.upload.FileUpload;
 import org.apache.wicket.markup.html.form.upload.FileUploadField;
 import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.request.resource.ResourceStreamResource;
 import org.apache.wicket.util.resource.AbstractResourceStream;
 import org.apache.wicket.util.resource.IResourceStream;
@@ -62,9 +63,11 @@ import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.googlecode.wicket.jquery.ui.form.button.AjaxButton;
-
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxButton;
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxLink;
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
 import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType;
 
 /**
  * Language Editor, add/insert/update Label and add/delete language contains several Forms and one list
@@ -157,13 +160,13 @@ public class LangPanel extends AdminBasePanel {
 
 			@Override
 			protected void onSubmit(AjaxRequestTarget target) {
-				FileUpload download = fileUploadField.getFileUpload();
+				FileUpload upload = fileUploadField.getFileUpload();
 				try {
-					if (download == null || download.getInputStream() == null) {
+					if (upload == null || upload.getInputStream() == null) {
 						feedback.error("File is empty");
 						return;
 					}
-					LabelDao.upload(language.getValue(), download.getInputStream());
+					LabelDao.upload(language.getValue(), upload.getInputStream());
 				} catch (Exception e) {
 					log.error("Exception on panel language editor import ", e);
 					feedback.error(e);
@@ -213,37 +216,30 @@ public class LangPanel extends AdminBasePanel {
 		});
 		langForm.add(download);
 
-		langForm.add(new AjaxButton("export"){
+		langForm.add(new BootstrapAjaxLink<String>("export", null, Buttons.Type.Outline_Primary, new ResourceModel("360")) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
-			protected void onSubmit(AjaxRequestTarget target) {
+			public void onClick(AjaxRequestTarget target) {
 				download.initiate(target);
 
 				// repaint the feedback panel so that it is hidden
 				target.add(feedback);
 			}
-
-			@Override
-			protected void onError(AjaxRequestTarget target) {
-				// repaint the feedback panel so errors are shown
-				target.add(feedback);
-			}
-
 		});
 
-		add(langForm);
 		final AddLanguageDialog addLang = new AddLanguageDialog("addLang", this);
-		add(addLang, new AjaxLink<Void>("addLangBtn") {
+		add(langForm, addLang);
+		langForm.add(new AjaxLink<Void>("addLangBtn") {
 			private static final long serialVersionUID = 1L;
 
 			@Override
 			public void onClick(AjaxRequestTarget target) {
-				addLang.open(target);
+				addLang.show(target);
 			}
 		});
 		add(BootstrapFileUploadBehavior.INSTANCE);
-		add(new ConfirmableAjaxBorder("deleteLangBtn", getString("80"), getString("833")) {
+		final BootstrapAjaxButton delLngBtn = new BootstrapAjaxButton("deleteLangBtn", Buttons.Type.Outline_Danger) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
@@ -254,13 +250,16 @@ public class LangPanel extends AdminBasePanel {
 				langForm.updateLanguages(target);
 				target.add(listContainer);
 			}
-		});
+		};
+		langForm.add(delLngBtn.setIconType(FontAwesome5IconType.times_s)
+				.add(CallbackFunctionHelper.newOkCancelDangerConfirm(this, getString("833"))));
 		super.onInitialize();
 	}
 
 	@Override
 	public BasePanel onMenuPanelLoad(IPartialPageRequestHandler handler) {
 		reinitJs(handler);
+		super.onMenuPanelLoad(handler);
 		return this;
 	}
 
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java
index 67e64ac..b4c81fe 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java
@@ -32,7 +32,7 @@ import org.apache.openmeetings.db.entity.server.OAuthServer;
 import org.apache.openmeetings.db.entity.server.OAuthServer.RequestInfoMethod;
 import org.apache.openmeetings.db.entity.server.OAuthServer.RequestTokenMethod;
 import org.apache.openmeetings.web.admin.AdminBaseForm;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.html.WebMarkupContainer;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
index 722b33a..c23fcb0 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
@@ -49,7 +49,7 @@ import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.web.admin.AdminBaseForm;
 import org.apache.openmeetings.web.admin.AdminUserChoiceProvider;
 import org.apache.openmeetings.web.app.ClientManager;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.util.RestrictiveChoiceProvider;
 import org.apache.openmeetings.web.util.RoomTypeDropDown;
 import org.apache.wicket.AttributeModifier;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.java
index 2209faa..4706743 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.web.common;
 
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.markup.html.form.Form;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.html
index 8928203..fed18cf 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.html
@@ -23,8 +23,12 @@
 <wicket:panel>
 	<div wicket:id="pagedPanel" class="pagedEntityListPanel"></div>
 	<form wicket:id="searchForm" class="searchForm">
-		<input type="text" wicket:id="searchText" wicket:message="title:714"/>
-		<input type="submit" wicket:id="search" wicket:message="value:714"/>
+		<div class="input-group input-group-sm">
+			<input type="text" wicket:id="searchText" wicket:message="title:714" class="form-control"/>
+			<div class="input-group-append">
+				<button wicket:id="search"></button>
+			</div>
+		</div>
 	</form>
 </wicket:panel>
 </html>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.java
index bbb1192..fb82c81 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagedEntityListPanel.java
@@ -18,9 +18,6 @@
  */
 package org.apache.openmeetings.web.common;
 
-import java.util.Arrays;
-import java.util.List;
-
 import org.apache.openmeetings.db.entity.IDataProviderEntity;
 import org.apache.openmeetings.web.admin.SearchableDataView;
 import org.apache.openmeetings.web.data.SearchableDataProvider;
@@ -29,17 +26,25 @@ import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
 
-import com.googlecode.wicket.jquery.ui.form.button.AjaxButton;
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxButton;
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
 
 public abstract class PagedEntityListPanel extends Panel {
 	private static final long serialVersionUID = 1L;
-	private List<Integer> numbers = Arrays.asList(10, 25, 50, 75, 100, 200);
+	private final SearchableDataView<? extends IDataProviderEntity> dataView;
 
 	public PagedEntityListPanel(String id, final SearchableDataView<? extends IDataProviderEntity> dataView) {
 		super(id);
+		this.dataView = dataView;
+	}
+
+	@Override
+	protected void onInitialize() {
+		super.onInitialize();
 
-		final PagingNavigatorPanel navPanel = new PagingNavigatorPanel("pagedPanel", dataView, numbers) {
+		final PagingNavigatorPanel navPanel = new PagingNavigatorPanel("pagedPanel", dataView) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
@@ -52,7 +57,7 @@ public abstract class PagedEntityListPanel extends Panel {
 		Form<Void> searchForm = new Form<>("searchForm");
 		add(searchForm.setOutputMarkupId(true));
 		searchForm.add(new TextField<>("searchText", new PropertyModel<String>(dp, "search")).setOutputMarkupId(true));
-		AjaxButton b = new AjaxButton("search", searchForm) {
+		BootstrapAjaxButton b = new BootstrapAjaxButton("search", new ResourceModel("714"), searchForm, Buttons.Type.Outline_Primary) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.html
index d8ce878..6e59ce9 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.html
@@ -22,8 +22,12 @@
 <html xmlns:wicket="http://wicket.apache.org">
 <wicket:panel>
 	<form wicket:id="pagingForm" class="pagedEntityListPanel">
-		<select wicket:id="entitiesPerPage"></select> <span
-			wicket:id="navigator" class="pagination pagination-sm">[dataview navigator]</span>
+		<div class="input-group input-group-sm">
+			<select wicket:id="entitiesPerPage" class="form-control"></select>
+			<div class="input-group-append">
+				<span wicket:id="navigator" class="mt-1 pagination pagination-sm">[dataview navigator]</span>
+			</div>
+		</div>
 	</form>
 </wicket:panel>
 </html>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.java
index b061a72..8a01bf7 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/PagingNavigatorPanel.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.web.common;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -31,15 +32,24 @@ import org.apache.wicket.model.PropertyModel;
 public abstract class PagingNavigatorPanel extends Panel {
 	private static final long serialVersionUID = 1L;
 	private int entitiesPerPage;
+	private final DataView<?> dataView;
+	private final List<Integer> numbers;
 
-	public PagingNavigatorPanel(String id, final DataView<?> dataView, List<Integer> numbers) {
-		this(id, dataView, numbers, 50);
+	public PagingNavigatorPanel(String id, final DataView<?> dataView) {
+		this(id, dataView, Arrays.asList(10, 25, 50, 75, 100, 200), 50);
 	}
 
-	public PagingNavigatorPanel(String id, final DataView<?> dataView, List<Integer> numbers, int _entitiesPerPage) {
+	public PagingNavigatorPanel(String id, final DataView<?> dataView, List<Integer> numbers, int entitiesPerPage) {
 		super(id);
 		setOutputMarkupId(true);
-		this.entitiesPerPage = _entitiesPerPage;
+		this.entitiesPerPage = entitiesPerPage;
+		this.dataView = dataView;
+		this.numbers = numbers;
+	}
+
+	@Override
+	protected void onInitialize() {
+		super.onInitialize();
 		dataView.setItemsPerPage(entitiesPerPage);
 		final Form<Void> f = new Form<>("pagingForm");
 		f.add(new OmPagingNavigator("navigator", dataView).setOutputMarkupId(true))
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/UploadableImagePanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/UploadableImagePanel.html
index 5e9a3bf..5caf8ce 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/UploadableImagePanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/UploadableImagePanel.html
@@ -24,10 +24,19 @@
 	<button type="button" class="btn btn-xs btn-secondary remove" wicket:id="remove">
 		<span aria-hidden="true">×</span>
 	</button>
-	<form wicket:id="form" class="img-upload"><div
-		class="fileinput fileinput-new m-0" data-provides="fileinput"><div
-		class="fileinput-preview" data-trigger="fileinput"><img wicket:id="img"/></div><div><span
-		class="btn btn-file btn btn-xs btn-primary"><wicket:message key="379"/><input
-		type="file" accept="image/*" wicket:id="image"/></span></div></div><span wicket:id="progress">[progressbar]</span></form>
+	<form wicket:id="form" class="img-upload">
+		<div class="fileinput fileinput-new m-0" data-provides="fileinput">
+			<div class="fileinput-preview" data-trigger="fileinput">
+				<img wicket:id="img"/>
+			</div>
+			<div>
+				<span class="btn btn-file btn-xs btn-primary">
+					<wicket:message key="379"/>
+					<input type="file" accept="image/*" wicket:id="image"/>
+				</span>
+			</div>
+		</div>
+		<span wicket:id="progress">[progressbar]</span>
+	</form>
 </wicket:extend>
 </html>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmableAjaxBorder.html
similarity index 100%
rename from openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.html
rename to openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmableAjaxBorder.html
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmableAjaxBorder.java
similarity index 99%
rename from openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.java
rename to openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmableAjaxBorder.java
index 6cb08fc..f48f701 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmableAjaxBorder.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.openmeetings.web.common;
+package org.apache.openmeetings.web.common.confirmation;
 
 import static org.apache.openmeetings.web.common.BasePanel.EVT_CLICK;
 
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmationBehavior.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmationBehavior.java
new file mode 100644
index 0000000..bd5bcfd
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmationBehavior.java
@@ -0,0 +1,84 @@
+/*
+ * 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.common.confirmation;
+
+import static de.agilecoders.wicket.jquery.JQuery.$;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.resource.JQueryPluginResourceReference;
+import org.apache.wicket.util.lang.Args;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.behavior.BootstrapJavascriptBehavior;
+import de.agilecoders.wicket.core.util.References;
+
+/**
+ * A behavior that shows a popover with OK/Cancel buttons to confirm an action.
+ * @since 0.9.12
+ */
+public class ConfirmationBehavior extends BootstrapJavascriptBehavior {
+    /** serialVersionUID. */
+    private static final long serialVersionUID = 1L;
+    /** Configuration. */
+    private final ConfirmationConfig config;
+    /** Jquery Selector (if you don't want to use the one of the component for singleton for example). */
+    private final String selector;
+
+    /**
+     * Constructor that uses the default configuration
+     */
+    public ConfirmationBehavior() {
+        this(null, new ConfirmationConfig());
+    }
+
+    /**
+     * Constructor that uses a custom configuration
+     * @param config configuration to use
+     */
+    public ConfirmationBehavior(ConfirmationConfig config) {
+        this(null, config);
+    }
+
+    /**
+     * Constructor that uses a custom configuration
+     * @param config configuration to use
+     * @param selector Jquery selector to use instead of the one of the component (for singleton's option)
+     */
+    public ConfirmationBehavior(String selector, ConfirmationConfig config) {
+        this.config = Args.notNull(config, "config");
+        this.selector = selector;
+    }
+
+    @Override
+    public void renderHead(Component component, IHeaderResponse response) {
+        super.renderHead(component, response);
+
+        References.renderWithFilter(response, JavaScriptHeaderItem.forReference(new JQueryPluginResourceReference(ConfirmationBehavior.class, "bootstrap-confirmation.js")));
+
+
+        if (selector == null) {
+            config.withRootSelector(component.getMarkupId());
+            response.render($(component).chain("confirmation", config).asDomReadyScript());
+        } else {
+            config.withRootSelector(selector);
+            response.render($(selector).chain("confirmation", config).asDomReadyScript());
+        }
+    }
+}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmationConfig.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmationConfig.java
new file mode 100644
index 0000000..0a6c94f
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/ConfirmationConfig.java
@@ -0,0 +1,116 @@
+/*
+ * 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.common.confirmation;
+
+import org.apache.wicket.util.lang.Args;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig;
+import de.agilecoders.wicket.jquery.AbstractConfig;
+import de.agilecoders.wicket.jquery.IKey;
+import de.agilecoders.wicket.jquery.Key;
+
+/**
+ * The configuration of {@link ConfirmationBehavior}.
+ * <br/>
+ * See <a href="http://mistic100.github.io/Bootstrap-Confirmation/#usage">Bootstrap Confirmation usage</a>
+ * for all possible options and their meaning.
+ */
+public class ConfirmationConfig extends AbstractConfig {
+    private static final long serialVersionUID = 1L;
+    private static final IKey<String> Title = new Key<>("title", "Are you sure?");
+    private static final IKey<Boolean> Singleton = new Key<>("singleton", Boolean.FALSE);
+    private static final IKey<Boolean> Popout = new Key<>("popout", Boolean.FALSE);
+    private static final IKey<String> BtnOkClass = new Key<>("btnOkClass", "btn-xs btn-primary");
+    private static final IKey<String> BtnOkIconClass = new Key<>("btnOkIconClass", "glyphicon glyphicon-ok");
+    private static final IKey<String> BtnOkIconContent = new Key<>("btnOkIconContent");
+    private static final IKey<String> BtnOkLabel = new Key<>("btnOkLabel", "Yes");
+    private static final IKey<String> BtnCancelClass = new Key<>("btnCancelClass", "btn-xs btn-default");
+    private static final IKey<String> BtnCancelIconClass = new Key<>("btnCancelIconClass", "glyphicon glyphicon-remove");
+    private static final IKey<String> BtnCancelIconContent = new Key<>("btnCancelIconContent");
+    private static final IKey<String> BtnCancelLabel = new Key<>("btnCancelLabel", "No");
+    private static final IKey<TooltipConfig.Placement> Placement = new Key<>("placement", TooltipConfig.Placement.top);
+    private static final IKey<String> RootSelector = new Key<>("rootSelector");
+
+
+
+    public ConfirmationConfig withTitle(String title) {
+        put(Title, title);
+        return this;
+    }
+
+    ConfirmationConfig withRootSelector(String rootSelector) {
+        put(RootSelector, rootSelector);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnOkClass(String btnOkClass) {
+        put(BtnOkClass, btnOkClass);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnOkIconClass(String btnOkIconClass) {
+        put(BtnOkIconClass, btnOkIconClass);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnOkIconContent(String btnOkIconContent) {
+        put(BtnOkIconContent, btnOkIconContent);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnOkLabel(String btnOkLabel) {
+        put(BtnOkLabel, btnOkLabel);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnCancelClass(String btnCancelClass) {
+        put(BtnCancelClass, btnCancelClass);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnCancelIconClass(String btnCancelIconClass) {
+        put(BtnCancelIconClass, btnCancelIconClass);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnCancelIconContent(String btnCancelIconContent) {
+        put(BtnCancelIconContent, btnCancelIconContent);
+        return this;
+    }
+
+    public ConfirmationConfig withBtnCancelLabel(String btnCancelLabel) {
+        put(BtnCancelLabel, btnCancelLabel);
+        return this;
+    }
+
+    public ConfirmationConfig withSingleton(boolean singleton) {
+        put(Singleton, singleton);
+        return this;
+    }
+
+    public ConfirmationConfig withPopout(boolean popout) {
+        put(Popout, popout);
+        return this;
+    }
+
+    public ConfirmationConfig withPlacement(TooltipConfig.Placement placement) {
+        put(Placement, Args.notNull(placement, "placement"));
+        return this;
+    }
+}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/bootstrap-confirmation.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/bootstrap-confirmation.js
new file mode 100644
index 0000000..1f0b16a
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/confirmation/bootstrap-confirmation.js
@@ -0,0 +1,597 @@
+/*!
+ * Bootstrap Confirmation (v4.1.0)
+ * @copyright 2013 Nimit Suwannagate <et...@hotmail.com>
+ * @copyright 2014-2018 Damien "Mistic" Sorel <co...@git.strangeplanet.fr>
+ * @licence Apache License, Version 2.0
+ */
+/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery'), require('bootstrap')) :
+  typeof define === 'function' && define.amd ? define(['jquery', 'bootstrap'], factory) :
+  (global = global || self, factory(global.jQuery));
+}(this, function ($) { 'use strict';
+
+  $ = $ && $.hasOwnProperty('default') ? $['default'] : $;
+
+  function _defineProperties(target, props) {
+    for (var i = 0; i < props.length; i++) {
+      var descriptor = props[i];
+      descriptor.enumerable = descriptor.enumerable || false;
+      descriptor.configurable = true;
+      if ("value" in descriptor) descriptor.writable = true;
+      Object.defineProperty(target, descriptor.key, descriptor);
+    }
+  }
+
+  function _createClass(Constructor, protoProps, staticProps) {
+    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+    if (staticProps) _defineProperties(Constructor, staticProps);
+    return Constructor;
+  }
+
+  function _defineProperty(obj, key, value) {
+    if (key in obj) {
+      Object.defineProperty(obj, key, {
+        value: value,
+        enumerable: true,
+        configurable: true,
+        writable: true
+      });
+    } else {
+      obj[key] = value;
+    }
+
+    return obj;
+  }
+
+  function _objectSpread(target) {
+    for (var i = 1; i < arguments.length; i++) {
+      var source = arguments[i] != null ? arguments[i] : {};
+      var ownKeys = Object.keys(source);
+
+      if (typeof Object.getOwnPropertySymbols === 'function') {
+        ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
+          return Object.getOwnPropertyDescriptor(source, sym).enumerable;
+        }));
+      }
+
+      ownKeys.forEach(function (key) {
+        _defineProperty(target, key, source[key]);
+      });
+    }
+
+    return target;
+  }
+
+  function _inheritsLoose(subClass, superClass) {
+    subClass.prototype = Object.create(superClass.prototype);
+    subClass.prototype.constructor = subClass;
+    subClass.__proto__ = superClass;
+  }
+
+  if (typeof $.fn.popover === 'undefined' || $.fn.popover.Constructor.VERSION.split('.').shift() !== '4') {
+    throw new Error('Bootstrap Confirmation 4 requires Bootstrap Popover 4');
+  }
+
+  var Popover = $.fn.popover.Constructor;
+
+  /**
+   * ------------------------------------------------------------------------
+   * Constants
+   * ------------------------------------------------------------------------
+   */
+
+  var NAME = 'confirmation';
+  var VERSION = '4.1.0';
+  var DATA_KEY = "bs." + NAME;
+  var EVENT_KEY = "." + DATA_KEY;
+  var JQUERY_NO_CONFLICT = $.fn[NAME];
+  var BTN_CLASS_BASE = 'h-100 d-flex align-items-center';
+  var BTN_CLASS_DEFAULT = 'btn btn-sm';
+
+  var DefaultType = _objectSpread({}, Popover.DefaultType, {
+    singleton: 'boolean',
+    popout: 'boolean',
+    copyAttributes: '(string|array)',
+    onConfirm: 'function',
+    onCancel: 'function',
+    btnOkClass: 'string',
+    btnOkLabel: 'string',
+    btnOkIconClass: 'string',
+    btnOkIconContent: 'string',
+    btnCancelClass: 'string',
+    btnCancelLabel: 'string',
+    btnCancelIconClass: 'string',
+    btnCancelIconContent: 'string',
+    buttons: 'array'
+  });
+
+  var Default = _objectSpread({}, Popover.Default, {
+    _attributes: {},
+    _selector: null,
+    placement: 'top',
+    title: 'Are you sure?',
+    trigger: 'click',
+    confirmationEvent: undefined,
+    content: '',
+    singleton: false,
+    popout: false,
+    copyAttributes: 'href target',
+    onConfirm: $.noop,
+    onCancel: $.noop,
+    btnOkClass: BTN_CLASS_DEFAULT + " btn-primary",
+    btnOkLabel: 'Yes',
+    btnOkIconClass: '',
+    btnOkIconContent: '',
+    btnCancelClass: BTN_CLASS_DEFAULT + " btn-secondary",
+    btnCancelLabel: 'No',
+    btnCancelIconClass: '',
+    btnCancelIconContent: '',
+    buttons: [],
+    // @formatter:off
+    template: "\n<div class=\"popover confirmation\">\n  <div class=\"arrow\"></div>\n  <h3 class=\"popover-header\"></h3>\n  <div class=\"popover-body\">\n    <p class=\"confirmation-content\"></p>\n    <div class=\"confirmation-buttons text-center\">\n      <div class=\"btn-group\"></div>\n    </div>\n  </div>\n</div>" // @formatter:on
+
+  });
+
+  if (Default.whiteList) {
+    Default.whiteList['*'].push('data-apply', 'data-dismiss');
+  }
+
+  var ClassName = {
+    FADE: 'fade',
+    SHOW: 'show'
+  };
+  var Selector = {
+    TITLE: '.popover-header',
+    CONTENT: '.confirmation-content',
+    BUTTONS: '.confirmation-buttons .btn-group'
+  };
+  var Keymap = {
+    13: 'Enter',
+    27: 'Escape',
+    39: 'ArrowRight',
+    40: 'ArrowDown'
+  };
+  var Event = {
+    HIDE: "hide" + EVENT_KEY,
+    HIDDEN: "hidden" + EVENT_KEY,
+    SHOW: "show" + EVENT_KEY,
+    SHOWN: "shown" + EVENT_KEY,
+    INSERTED: "inserted" + EVENT_KEY,
+    CLICK: "click" + EVENT_KEY,
+    FOCUSIN: "focusin" + EVENT_KEY,
+    FOCUSOUT: "focusout" + EVENT_KEY,
+    MOUSEENTER: "mouseenter" + EVENT_KEY,
+    MOUSELEAVE: "mouseleave" + EVENT_KEY,
+    CONFIRMED: "confirmed" + EVENT_KEY,
+    CANCELED: "canceled" + EVENT_KEY,
+    KEYUP: "keyup" + EVENT_KEY
+  };
+  /**
+   * ------------------------------------------------------------------------
+   * Class Definition
+   * ------------------------------------------------------------------------
+   */
+  // keep track of the last openned confirmation for keyboard navigation
+
+  var activeConfirmation;
+
+  var Confirmation =
+  /*#__PURE__*/
+  function (_Popover) {
+    _inheritsLoose(Confirmation, _Popover);
+
+    _createClass(Confirmation, null, [{
+      key: "VERSION",
+      // Getters
+      get: function get() {
+        return VERSION;
+      }
+    }, {
+      key: "Default",
+      get: function get() {
+        return Default;
+      }
+    }, {
+      key: "NAME",
+      get: function get() {
+        return NAME;
+      }
+    }, {
+      key: "DATA_KEY",
+      get: function get() {
+        return DATA_KEY;
+      }
+    }, {
+      key: "Event",
+      get: function get() {
+        return Event;
+      }
+    }, {
+      key: "EVENT_KEY",
+      get: function get() {
+        return EVENT_KEY;
+      }
+    }, {
+      key: "DefaultType",
+      get: function get() {
+        return DefaultType;
+      } // Constructor
+
+    }]);
+
+    function Confirmation(element, config) {
+      var _this;
+
+      _this = _Popover.call(this, element, config) || this;
+      element.setAttribute('title', element.getAttribute('data-original-title'));
+      element.setAttribute('data-original-title', '');
+
+      if ((_this.config.popout || _this.config.singleton) && !_this.config.rootSelector) {
+        throw new Error('The rootSelector option is required to use popout and singleton features since jQuery 3.');
+      } // keep trace of selectors
+
+
+      _this._isDelegate = false;
+
+      if (config.selector) {
+        // container of buttons
+        config._selector = config.rootSelector + " " + config.selector;
+        _this.config._selector = config._selector;
+      } else if (config._selector) {
+        // children of container
+        _this.config._selector = config._selector;
+        _this._isDelegate = true;
+      } else {
+        // standalone
+        _this.config._selector = config.rootSelector;
+      }
+
+      if (_this.config.confirmationEvent === undefined) {
+        _this.config.confirmationEvent = _this.config.trigger;
+      }
+
+      if (!_this.config.selector) {
+        _this._copyAttributes();
+      }
+
+      _this._setConfirmationListeners();
+
+      return _this;
+    } // Overrides
+
+
+    var _proto = Confirmation.prototype;
+
+    _proto.isWithContent = function isWithContent() {
+      return true;
+    };
+
+    _proto.setContent = function setContent() {
+      var $tip = $(this.getTipElement());
+
+      var content = this._getContent();
+
+      if (typeof content === 'function') {
+        content = content.call(this.element);
+      }
+
+      this.setElementContent($tip.find(Selector.TITLE), this.getTitle());
+      $tip.find(Selector.CONTENT).toggle(!!content);
+
+      if (content) {
+        this.setElementContent($tip.find(Selector.CONTENT), content);
+      }
+
+      if (this.config.buttons.length > 0) {
+        this._setButtons($tip, this.config.buttons);
+      } else {
+        this._setStandardButtons($tip);
+      }
+
+      $tip.removeClass(ClassName.FADE + " " + ClassName.SHOW);
+
+      this._setupKeyupEvent();
+    };
+
+    _proto.dispose = function dispose() {
+      $('body').off(Event.CLICK + "." + this.uid);
+      this.eventBody = false;
+
+      this._cleanKeyupEvent();
+
+      _Popover.prototype.dispose.call(this);
+    };
+
+    _proto.hide = function hide(callback) {
+      this._cleanKeyupEvent();
+
+      _Popover.prototype.hide.call(this, callback);
+    } // Private
+
+    /**
+     * Copy the value of `copyAttributes` on the config object
+     * @private
+     */
+    ;
+
+    _proto._copyAttributes = function _copyAttributes() {
+      var _this2 = this;
+
+      this.config._attributes = {};
+
+      if (this.config.copyAttributes) {
+        if (typeof this.config.copyAttributes === 'string') {
+          this.config.copyAttributes = this.config.copyAttributes.split(' ');
+        }
+      } else {
+        this.config.copyAttributes = [];
+      }
+
+      this.config.copyAttributes.forEach(function (attr) {
+        _this2.config._attributes[attr] = $(_this2.element).attr(attr);
+      });
+    }
+    /**
+     * Custom event listeners for popouts and singletons
+     * @private
+     */
+    ;
+
+    _proto._setConfirmationListeners = function _setConfirmationListeners() {
+      var self = this;
+
+      if (!this.config.selector) {
+        // cancel original event
+        $(this.element).on(this.config.trigger, function (e, ack) {
+          if (!ack) {
+            e.preventDefault();
+            e.stopPropagation();
+            e.stopImmediatePropagation();
+          }
+        }); // manage singleton
+
+        $(this.element).on(Event.SHOWN, function () {
+          if (self.config.singleton) {
+            // close all other popover already initialized
+            $(self.config._selector).not($(this)).filter(function () {
+              return $(this).data(DATA_KEY) !== undefined;
+            }).confirmation('hide');
+          }
+        });
+      } else {
+        // cancel original event
+        $(this.element).on(this.config.trigger, this.config.selector, function (e, ack) {
+          if (!ack) {
+            e.preventDefault();
+            e.stopPropagation();
+            e.stopImmediatePropagation();
+          }
+        });
+      }
+
+      if (!this._isDelegate) {
+        // manage popout
+        this.eventBody = false;
+        this.uid = this.element.id || Confirmation.getUID(NAME + "_group");
+        $(this.element).on(Event.SHOWN, function () {
+          if (self.config.popout && !self.eventBody) {
+            self.eventBody = $('body').on(Event.CLICK + "." + self.uid, function (e) {
+              if ($(self.config._selector).is(e.target) || $(self.config._selector).has(e.target).length > 0) {
+                return;
+              } // close all popover already initialized
+
+
+              $(self.config._selector).filter(function () {
+                return $(this).data(DATA_KEY) !== undefined;
+              }).confirmation('hide');
+              $('body').off(Event.CLICK + "." + self.uid);
+              self.eventBody = false;
+            });
+          }
+        });
+      }
+    }
+    /**
+     * Init the standard ok/cancel buttons
+     * @param $tip
+     * @private
+     */
+    ;
+
+    _proto._setStandardButtons = function _setStandardButtons($tip) {
+      var buttons = [{
+        class: this.config.btnOkClass,
+        label: this.config.btnOkLabel,
+        iconClass: this.config.btnOkIconClass,
+        iconContent: this.config.btnOkIconContent,
+        attr: this.config._attributes
+      }, {
+        class: this.config.btnCancelClass,
+        label: this.config.btnCancelLabel,
+        iconClass: this.config.btnCancelIconClass,
+        iconContent: this.config.btnCancelIconContent,
+        cancel: true
+      }];
+
+      this._setButtons($tip, buttons);
+    }
+    /**
+     * Init the buttons
+     * @param $tip
+     * @param buttons
+     * @private
+     */
+    ;
+
+    _proto._setButtons = function _setButtons($tip, buttons) {
+      var self = this;
+      var $group = $tip.find(Selector.BUTTONS).empty();
+      buttons.forEach(function (button) {
+        var btn = $('<a href="#"></a>').addClass(BTN_CLASS_BASE).addClass(button.class || BTN_CLASS_DEFAULT + " btn-secondary").html(button.label || '').attr(button.attr || {});
+
+        if (button.iconClass || button.iconContent) {
+          btn.prepend($('<i></i>').addClass(button.iconClass || '').text(button.iconContent || ''));
+        }
+
+        btn.one('click', function (e) {
+          if ($(this).attr('href') === '#') {
+            e.preventDefault();
+          }
+
+          if (button.onClick) {
+            button.onClick.call($(self.element));
+          }
+
+          if (button.cancel) {
+            self.config.onCancel.call(self.element, button.value);
+            $(self.element).trigger(Event.CANCELED, [button.value]);
+          } else {
+            self.config.onConfirm.call(self.element, button.value);
+            $(self.element).trigger(Event.CONFIRMED, [button.value]);
+            $(self.element).trigger(self.config.confirmationEvent, [true]);
+          }
+
+          self.hide();
+        });
+        $group.append(btn);
+      });
+    }
+    /**
+     * Install the keyboatd event handler
+     * @private
+     */
+    ;
+
+    _proto._setupKeyupEvent = function _setupKeyupEvent() {
+      activeConfirmation = this;
+      $(window).off(Event.KEYUP).on(Event.KEYUP, this._onKeyup.bind(this));
+    }
+    /**
+     * Remove the keyboard event handler
+     * @private
+     */
+    ;
+
+    _proto._cleanKeyupEvent = function _cleanKeyupEvent() {
+      if (activeConfirmation === this) {
+        activeConfirmation = undefined;
+        $(window).off(Event.KEYUP);
+      }
+    }
+    /**
+     * Event handler for keyboard navigation
+     * @param event
+     * @private
+     */
+    ;
+
+    _proto._onKeyup = function _onKeyup(event) {
+      if (!this.tip) {
+        this._cleanKeyupEvent();
+
+        return;
+      }
+
+      var $tip = $(this.getTipElement());
+      var key = event.key || Keymap[event.keyCode || event.which];
+      var $group = $tip.find(Selector.BUTTONS);
+      var $active = $group.find('.active');
+      var $next;
+
+      switch (key) {
+        case 'Escape':
+          this.hide();
+          break;
+
+        case 'ArrowRight':
+          if ($active.length && $active.next().length) {
+            $next = $active.next();
+          } else {
+            $next = $group.children().first();
+          }
+
+          $active.removeClass('active');
+          $next.addClass('active').focus();
+          break;
+
+        case 'ArrowLeft':
+          if ($active.length && $active.prev().length) {
+            $next = $active.prev();
+          } else {
+            $next = $group.children().last();
+          }
+
+          $active.removeClass('active');
+          $next.addClass('active').focus();
+          break;
+
+        default:
+          break;
+      }
+    } // Static
+
+    /**
+     * Generates an uui, copied from Bootrap's utils
+     * @param {string} prefix
+     * @returns {string}
+     */
+    ;
+
+    Confirmation.getUID = function getUID(prefix) {
+      var uid = prefix;
+
+      do {
+        // eslint-disable-next-line no-bitwise
+        uid += ~~(Math.random() * 1000000); // "~~" acts like a faster Math.floor() here
+      } while (document.getElementById(uid));
+
+      return uid;
+    };
+
+    Confirmation._jQueryInterface = function _jQueryInterface(config) {
+      return this.each(function () {
+        var data = $(this).data(DATA_KEY);
+
+        var _config = typeof config === 'object' ? config : {};
+
+        _config.rootSelector = $(this).selector || _config.rootSelector; // this.selector removed in jQuery > 3
+
+        if (!data && /destroy|hide/.test(config)) {
+          return;
+        }
+
+        if (!data) {
+          data = new Confirmation(this, _config);
+          $(this).data(DATA_KEY, data);
+        }
+
+        if (typeof config === 'string') {
+          if (typeof data[config] === 'undefined') {
+            throw new TypeError("No method named \"" + config + "\"");
+          }
+
+          data[config]();
+        }
+      });
+    };
+
+    return Confirmation;
+  }(Popover);
+  /**
+   * ------------------------------------------------------------------------
+   * jQuery
+   * ------------------------------------------------------------------------
+   */
+
+
+  $.fn[NAME] = Confirmation._jQueryInterface;
+  $.fn[NAME].Constructor = Confirmation;
+
+  $.fn[NAME].noConflict = function () {
+    $.fn[NAME] = JQUERY_NO_CONFLICT;
+    return Confirmation._jQueryInterface;
+  };
+
+}));
+//# sourceMappingURL=bootstrap-confirmation.js.map
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/tree/FileTreePanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/tree/FileTreePanel.java
index b3ff049..310469e 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/tree/FileTreePanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/tree/FileTreePanel.java
@@ -42,9 +42,9 @@ import org.apache.openmeetings.db.entity.file.BaseFileItem;
 import org.apache.openmeetings.db.entity.file.BaseFileItem.Type;
 import org.apache.openmeetings.db.entity.file.FileItem;
 import org.apache.openmeetings.db.entity.record.Recording;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder.ConfirmableBorderDialog;
 import org.apache.openmeetings.web.common.NameDialog;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder.ConfirmableBorderDialog;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxEventBehavior;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java
index 6826db2..63a01ad 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomFilePanel.java
@@ -26,8 +26,8 @@ import org.apache.openmeetings.db.dao.file.FileItemDao;
 import org.apache.openmeetings.db.dao.record.RecordingDao;
 import org.apache.openmeetings.db.dto.record.RecordingContainerData;
 import org.apache.openmeetings.db.entity.file.BaseFileItem;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder.ConfirmableBorderDialog;
 import org.apache.openmeetings.web.common.NameDialog;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder.ConfirmableBorderDialog;
 import org.apache.openmeetings.web.common.tree.FileTreePanel;
 import org.apache.openmeetings.web.room.RoomPanel;
 import org.apache.wicket.Component;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java
index 8bad4ff..88b6a41 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java
@@ -37,9 +37,9 @@ import org.apache.openmeetings.db.util.ws.RoomMessage;
 import org.apache.openmeetings.db.util.ws.TextRoomMessage;
 import org.apache.openmeetings.web.app.ClientManager;
 import org.apache.openmeetings.web.app.WebSession;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder.ConfirmableBorderDialog;
 import org.apache.openmeetings.web.common.NameDialog;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder.ConfirmableBorderDialog;
 import org.apache.openmeetings.web.room.RoomPanel;
 import org.apache.openmeetings.web.room.RoomPanel.Action;
 import org.apache.openmeetings.web.room.VideoSettings;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/chat/ChatToolbar.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/chat/ChatToolbar.java
index 761c17f..acfc083 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/chat/ChatToolbar.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/chat/ChatToolbar.java
@@ -37,7 +37,7 @@ import org.apache.openmeetings.db.dao.basic.ChatDao;
 import org.apache.openmeetings.db.entity.basic.ChatMessage;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.web.app.ClientManager;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.pages.BasePage;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.Component;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfileForm.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfileForm.html
index 48f4112..4781bc5 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfileForm.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfileForm.html
@@ -23,35 +23,30 @@
 <wicket:panel>
 	<div class="actions" wicket:id="buttons"></div>
 	<div class="profile-edit-form overflow-y-auto">
-		<table class="w-100 h-100">
-			<tr>
-				<td rowspan="2">
-					<fieldset class="ui-widget-content">
-						<legend class="ui-widget-header">
-							<wicket:message key="143" />
-						</legend>
-						<button type="button" wicket:id="changePwd" id="changePwd"><wicket:message key="327"/></button>
-						<div class="formelement" wicket:enclosure="passwd">
-							<label wicket:for="passwd"><wicket:message key="current.password" /></label><input type="password" wicket:id="passwd" />
-						</div>
-						<form wicket:id="general"></form>
-					</fieldset>
-				</td>
-				<td>
-					<div wicket:id="img"></div>
-				</td>
-			</tr>
-			<tr>
-				<td>
-					<fieldset class="ui-widget-content">
-						<legend class="ui-widget-header">
-							<wicket:message key="1159" />
-						</legend>
-						<form wicket:id="comunity"></form>
-					</fieldset>
-				</td>
-			</tr>
-		</table>
+		<div class="h-100 row m-0">
+			<div class="col-6">
+				<fieldset class="">
+					<legend class="">
+						<wicket:message key="143" />
+					</legend>
+					<button type="button" wicket:id="changePwd" id="changePwd"><wicket:message key="327"/></button>
+					<div class="formelement" wicket:enclosure="passwd">
+						<label class="col-3 text-right" wicket:for="passwd"><wicket:message key="current.password" /></label>
+						<input type="password" wicket:id="passwd" class="col-8"/>
+					</div>
+					<form wicket:id="general"></form>
+				</fieldset>
+			</div>
+			<div class="col-6">
+				<div wicket:id="img" class="pb-5"></div>
+				<fieldset class="mt-5">
+					<legend class="">
+						<wicket:message key="1159" />
+					</legend>
+					<form wicket:id="comunity"></form>
+				</fieldset>
+			</div>
+		</div>
 	</div>
 	<div class="pt-2 text-center">
 		<a wicket:id="link" target="_blank"><wicket:message key="register.privacy.statement"/></a>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfilePanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfilePanel.html
index b80c4a7..82b066c 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfilePanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/EditProfilePanel.html
@@ -21,7 +21,7 @@
 <!DOCTYPE html>
 <html xmlns:wicket="http://wicket.apache.org">
 <wicket:panel>
-	<form wicket:id="form" class="adminForm edit-profile"/>
+	<form wicket:id="form" class="adminForm edit-profile container"/>
 	<div wicket:id="changePasswd"></div>
 </wicket:panel>
 </html>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/InvitationDetails.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/InvitationDetails.java
index 8333bdd..293d312 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/InvitationDetails.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/InvitationDetails.java
@@ -26,7 +26,7 @@ import java.util.Date;
 import org.apache.openmeetings.db.dao.room.InvitationDao;
 import org.apache.openmeetings.db.entity.room.Invitation;
 import org.apache.openmeetings.db.entity.room.Invitation.Valid;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.util.DateLabel;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.html.WebMarkupContainer;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/MessagesContactsPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/MessagesContactsPanel.java
index 00e739c..17425eb 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/MessagesContactsPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/MessagesContactsPanel.java
@@ -45,10 +45,10 @@ import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.UserContact;
 import org.apache.openmeetings.web.admin.SearchableDataView;
 import org.apache.openmeetings.web.app.Application;
-import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.common.NameDialog;
 import org.apache.openmeetings.web.common.PagedEntityListPanel;
 import org.apache.openmeetings.web.common.UserBasePanel;
+import org.apache.openmeetings.web.common.confirmation.ConfirmableAjaxBorder;
 import org.apache.openmeetings.web.data.DataViewContainer;
 import org.apache.openmeetings.web.data.OmOrderByBorder;
 import org.apache.openmeetings.web.data.SearchableDataProvider;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/CallbackFunctionHelper.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/CallbackFunctionHelper.java
index 078fbfd..cca93f8 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/CallbackFunctionHelper.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/util/CallbackFunctionHelper.java
@@ -20,6 +20,8 @@ import static java.util.UUID.randomUUID;
 
 import java.io.Serializable;
 
+import org.apache.openmeetings.web.common.confirmation.ConfirmationBehavior;
+import org.apache.openmeetings.web.common.confirmation.ConfirmationConfig;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
@@ -27,9 +29,6 @@ import org.apache.wicket.ajax.attributes.CallbackParameter;
 import org.apache.wicket.markup.head.JavaScriptHeaderItem;
 import org.apache.wicket.util.string.StringValue;
 
-import de.agilecoders.wicket.extensions.markup.html.bootstrap.confirmation.ConfirmationBehavior;
-import de.agilecoders.wicket.extensions.markup.html.bootstrap.confirmation.ConfirmationConfig;
-
 public class CallbackFunctionHelper {
 	private CallbackFunctionHelper() {}
 
diff --git a/openmeetings-web/src/main/webapp/css/raw-admin.css b/openmeetings-web/src/main/webapp/css/raw-admin.css
index fb3238a..36132b7 100644
--- a/openmeetings-web/src/main/webapp/css/raw-admin.css
+++ b/openmeetings-web/src/main/webapp/css/raw-admin.css
@@ -139,9 +139,6 @@
 	right: 0px;
 	background-color: #A1A1A1;
 }
-.addLanguagePanel {
-	display: inline-block;
-}
 .addLanguagePanel select {
 	padding: 0px 0px;
 	border: solid 1px #aacfe4;
diff --git a/openmeetings-web/src/main/webapp/css/raw-general.css b/openmeetings-web/src/main/webapp/css/raw-general.css
index e52807c..cf6221b 100644
--- a/openmeetings-web/src/main/webapp/css/raw-general.css
+++ b/openmeetings-web/src/main/webapp/css/raw-general.css
@@ -96,7 +96,7 @@ html, body {
 	max-width: 100%;
 	height: calc(100% - 50px);
 }
-.pagedEntityListPanel {
+.pagedEntityListPanel, .addLanguagePanel {
 	margin-left: 2px;
 	display: inline-block;
 }