Merge remote-tracking branch 'origin/dev-1.2' into development
This commit is contained in:
commit
2e4dcca89e
51 changed files with 884 additions and 412 deletions
|
@ -35,7 +35,7 @@ public class ProctoringServiceSettings implements Entity {
|
||||||
TOWN_HALL,
|
TOWN_HALL,
|
||||||
ONE_TO_ONE,
|
ONE_TO_ONE,
|
||||||
BROADCAST,
|
BROADCAST,
|
||||||
ENABLE_CHAT
|
ENABLE_CHAT,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String ATTR_ENABLE_PROCTORING = "enableProctoring";
|
public static final String ATTR_ENABLE_PROCTORING = "enableProctoring";
|
||||||
|
|
|
@ -61,9 +61,9 @@ public final class LmsSetup implements GrantEntity, Activatable {
|
||||||
/** The Moodle binding features only the course access API so far */
|
/** The Moodle binding features only the course access API so far */
|
||||||
MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */),
|
MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */),
|
||||||
/** The Ans Delft binding is on the way */
|
/** The Ans Delft binding is on the way */
|
||||||
ANS_DELFT(Features.COURSE_API, Features.SEB_RESTRICTION),
|
ANS_DELFT(),
|
||||||
/** The OpenOLAT binding is on the way */
|
/** The OpenOLAT binding is on the way */
|
||||||
OPEN_OLAT(Features.COURSE_API, Features.SEB_RESTRICTION);
|
OPEN_OLAT();
|
||||||
|
|
||||||
public final EnumSet<Features> features;
|
public final EnumSet<Features> features;
|
||||||
|
|
||||||
|
|
|
@ -34,12 +34,12 @@ public final class ClientConnection implements GrantEntity {
|
||||||
|
|
||||||
public final boolean connectingStatus;
|
public final boolean connectingStatus;
|
||||||
public final boolean establishedStatus;
|
public final boolean establishedStatus;
|
||||||
public final boolean indicatorActiveStatus;
|
public final boolean clientActiveStatus;
|
||||||
|
|
||||||
ConnectionStatus(final boolean connectingStatus, final boolean establishedStatus) {
|
ConnectionStatus(final boolean connectingStatus, final boolean establishedStatus) {
|
||||||
this.connectingStatus = connectingStatus;
|
this.connectingStatus = connectingStatus;
|
||||||
this.establishedStatus = establishedStatus;
|
this.establishedStatus = establishedStatus;
|
||||||
this.indicatorActiveStatus = connectingStatus || establishedStatus;
|
this.clientActiveStatus = connectingStatus || establishedStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,10 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class RemoteProctoringRoom {
|
public class RemoteProctoringRoom {
|
||||||
|
|
||||||
|
public static final String ATTR_IS_OPEN = "isOpen";
|
||||||
|
|
||||||
public static final RemoteProctoringRoom NULL_ROOM = new RemoteProctoringRoom(
|
public static final RemoteProctoringRoom NULL_ROOM = new RemoteProctoringRoom(
|
||||||
null, null, null, null, null, false, null, null, null);
|
null, null, null, null, null, false, null, null, null, false);
|
||||||
|
|
||||||
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID)
|
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID)
|
||||||
public final Long id;
|
public final Long id;
|
||||||
|
@ -52,6 +54,9 @@ public class RemoteProctoringRoom {
|
||||||
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA)
|
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA)
|
||||||
public final String additionalRoomData;
|
public final String additionalRoomData;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_IS_OPEN)
|
||||||
|
public final Boolean isOpen;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public RemoteProctoringRoom(
|
public RemoteProctoringRoom(
|
||||||
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID) final Long id,
|
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID) final Long id,
|
||||||
|
@ -62,7 +67,8 @@ public class RemoteProctoringRoom {
|
||||||
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_TOWNHALL_ROOM) final Boolean townhallRoom,
|
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_TOWNHALL_ROOM) final Boolean townhallRoom,
|
||||||
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_BREAK_OUT_CONNECTIONS) final Collection<String> breakOutConnections,
|
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_BREAK_OUT_CONNECTIONS) final Collection<String> breakOutConnections,
|
||||||
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_JOIN_KEY) final CharSequence joinKey,
|
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_JOIN_KEY) final CharSequence joinKey,
|
||||||
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA) final String additionalRoomData) {
|
@JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA) final String additionalRoomData,
|
||||||
|
@JsonProperty(ATTR_IS_OPEN) final Boolean isOpen) {
|
||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.examId = examId;
|
this.examId = examId;
|
||||||
|
@ -73,6 +79,7 @@ public class RemoteProctoringRoom {
|
||||||
this.breakOutConnections = Utils.immutableCollectionOf(breakOutConnections);
|
this.breakOutConnections = Utils.immutableCollectionOf(breakOutConnections);
|
||||||
this.joinKey = joinKey;
|
this.joinKey = joinKey;
|
||||||
this.additionalRoomData = additionalRoomData;
|
this.additionalRoomData = additionalRoomData;
|
||||||
|
this.isOpen = isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
|
@ -111,6 +118,10 @@ public class RemoteProctoringRoom {
|
||||||
return this.additionalRoomData;
|
return this.additionalRoomData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getIsOpen() {
|
||||||
|
return this.isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
@ -128,6 +139,12 @@ public class RemoteProctoringRoom {
|
||||||
builder.append(this.townhallRoom);
|
builder.append(this.townhallRoom);
|
||||||
builder.append(", breakOutConnections=");
|
builder.append(", breakOutConnections=");
|
||||||
builder.append(this.breakOutConnections);
|
builder.append(this.breakOutConnections);
|
||||||
|
builder.append(", joinKey=");
|
||||||
|
builder.append(this.joinKey);
|
||||||
|
builder.append(", additionalRoomData=");
|
||||||
|
builder.append(this.additionalRoomData);
|
||||||
|
builder.append(", isOpen=");
|
||||||
|
builder.append(this.isOpen);
|
||||||
builder.append("]");
|
builder.append("]");
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,12 @@ public class RAPConfiguration implements ApplicationConfiguration {
|
||||||
properties.put(WebClient.FAVICON, "fav_icon");
|
properties.put(WebClient.FAVICON, "fav_icon");
|
||||||
|
|
||||||
application.addEntryPoint(guiEntrypoint, new RAPSpringEntryPointFactory(), properties);
|
application.addEntryPoint(guiEntrypoint, new RAPSpringEntryPointFactory(), properties);
|
||||||
|
|
||||||
|
properties.put(WebClient.PAGE_TITLE, "SEB Server Proctoring");
|
||||||
|
properties.put(WebClient.BODY_HTML, "<big>Loading Application<big>");
|
||||||
|
properties.put(WebClient.THEME_ID, DEFAULT_THEME_NAME);
|
||||||
|
properties.put(WebClient.FAVICON, "fav_icon");
|
||||||
|
|
||||||
application.addEntryPoint(proctoringEntrypoint, new RAPRemoteProcotringEntryPointFactory(), properties);
|
application.addEntryPoint(proctoringEntrypoint, new RAPRemoteProcotringEntryPointFactory(), properties);
|
||||||
|
|
||||||
} catch (final RuntimeException re) {
|
} catch (final RuntimeException re) {
|
||||||
|
|
|
@ -139,11 +139,12 @@ public class ExamProctoringSettings {
|
||||||
.valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE));
|
.valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE));
|
||||||
|
|
||||||
final String features = form.getFieldValue(ProctoringServiceSettings.ATTR_ENABLED_FEATURES);
|
final String features = form.getFieldValue(ProctoringServiceSettings.ATTR_ENABLED_FEATURES);
|
||||||
final EnumSet<ProctoringFeature> featureFlags =
|
final EnumSet<ProctoringFeature> featureFlags = (StringUtils.isNotBlank(features))
|
||||||
EnumSet.copyOf(Arrays.asList(StringUtils.split(features, Constants.LIST_SEPARATOR))
|
? EnumSet.copyOf(Arrays.asList(StringUtils.split(features, Constants.LIST_SEPARATOR))
|
||||||
.stream()
|
.stream()
|
||||||
.map(str -> ProctoringFeature.valueOf(str))
|
.map(str -> ProctoringFeature.valueOf(str))
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()))
|
||||||
|
: EnumSet.noneOf(ProctoringFeature.class);
|
||||||
|
|
||||||
examProctoring = new ProctoringServiceSettings(
|
examProctoring = new ProctoringServiceSettings(
|
||||||
Long.parseLong(entityKey.modelId),
|
Long.parseLong(entityKey.modelId),
|
||||||
|
|
|
@ -12,10 +12,12 @@ import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.rap.rwt.RWT;
|
import org.eclipse.rap.rwt.RWT;
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
|
import org.eclipse.swt.internal.widgets.IControlAdapter;
|
||||||
import org.eclipse.swt.layout.GridData;
|
import org.eclipse.swt.layout.GridData;
|
||||||
import org.eclipse.swt.layout.GridLayout;
|
import org.eclipse.swt.layout.GridLayout;
|
||||||
import org.eclipse.swt.widgets.Button;
|
import org.eclipse.swt.widgets.Button;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.Control;
|
||||||
import org.eclipse.swt.widgets.Label;
|
import org.eclipse.swt.widgets.Label;
|
||||||
import org.eclipse.swt.widgets.MessageBox;
|
import org.eclipse.swt.widgets.MessageBox;
|
||||||
import org.eclipse.swt.widgets.Text;
|
import org.eclipse.swt.widgets.Text;
|
||||||
|
@ -27,6 +29,8 @@ import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||||
|
@ -43,6 +47,11 @@ public class LoginPage implements TemplateComposer {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(LoginPage.class);
|
private static final Logger log = LoggerFactory.getLogger(LoginPage.class);
|
||||||
|
|
||||||
|
private static final LocTextKey TEXT_REGISTER = new LocTextKey("sebserver.login.register");
|
||||||
|
private static final LocTextKey TEXT_LOGIN = new LocTextKey("sebserver.login.login");
|
||||||
|
private static final LocTextKey TEXT_PWD = new LocTextKey("sebserver.login.pwd");
|
||||||
|
private static final LocTextKey TEXT_USERNAME = new LocTextKey("sebserver.login.username");
|
||||||
|
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
private final AuthorizationContextHolder authorizationContextHolder;
|
private final AuthorizationContextHolder authorizationContextHolder;
|
||||||
private final WidgetFactory widgetFactory;
|
private final WidgetFactory widgetFactory;
|
||||||
|
@ -67,7 +76,6 @@ public class LoginPage implements TemplateComposer {
|
||||||
public void compose(final PageContext pageContext) {
|
public void compose(final PageContext pageContext) {
|
||||||
final Composite parent = pageContext.getParent();
|
final Composite parent = pageContext.getParent();
|
||||||
WidgetFactory.setTestId(parent, "login-page");
|
WidgetFactory.setTestId(parent, "login-page");
|
||||||
WidgetFactory.setARIARole(parent, "composite");
|
|
||||||
|
|
||||||
final Composite loginGroup = new Composite(parent, SWT.NONE);
|
final Composite loginGroup = new Composite(parent, SWT.NONE);
|
||||||
final GridLayout rowLayout = new GridLayout();
|
final GridLayout rowLayout = new GridLayout();
|
||||||
|
@ -76,16 +84,17 @@ public class LoginPage implements TemplateComposer {
|
||||||
loginGroup.setLayout(rowLayout);
|
loginGroup.setLayout(rowLayout);
|
||||||
loginGroup.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN.key);
|
loginGroup.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN.key);
|
||||||
|
|
||||||
final Label name = this.widgetFactory.labelLocalized(loginGroup, "sebserver.login.username");
|
final Label name = this.widgetFactory.labelLocalized(loginGroup, TEXT_USERNAME);
|
||||||
name.setLayoutData(new GridData(300, -1));
|
name.setLayoutData(new GridData(300, -1));
|
||||||
name.setAlignment(SWT.BOTTOM);
|
name.setAlignment(SWT.BOTTOM);
|
||||||
final Text loginName = this.widgetFactory.textInput(loginGroup);
|
final Text loginName = this.widgetFactory.textInput(loginGroup, TEXT_USERNAME);
|
||||||
loginName.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
|
loginName.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
|
||||||
|
|
||||||
GridData gridData = new GridData(SWT.FILL, SWT.TOP, false, false);
|
GridData gridData = new GridData(SWT.FILL, SWT.TOP, false, false);
|
||||||
gridData.verticalIndent = 10;
|
gridData.verticalIndent = 10;
|
||||||
final Label pwd = this.widgetFactory.labelLocalized(loginGroup, "sebserver.login.pwd");
|
final Label pwd = this.widgetFactory.labelLocalized(loginGroup, TEXT_PWD);
|
||||||
pwd.setLayoutData(gridData);
|
pwd.setLayoutData(gridData);
|
||||||
final Text loginPassword = this.widgetFactory.passwordInput(loginGroup);
|
final Text loginPassword = this.widgetFactory.passwordInput(loginGroup, TEXT_PWD);
|
||||||
loginPassword.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
|
loginPassword.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
|
||||||
|
|
||||||
final Composite buttons = new Composite(loginGroup, SWT.NONE);
|
final Composite buttons = new Composite(loginGroup, SWT.NONE);
|
||||||
|
@ -93,7 +102,7 @@ public class LoginPage implements TemplateComposer {
|
||||||
buttons.setLayout(new GridLayout(2, false));
|
buttons.setLayout(new GridLayout(2, false));
|
||||||
buttons.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key);
|
buttons.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key);
|
||||||
|
|
||||||
final Button loginButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.login");
|
final Button loginButton = this.widgetFactory.buttonLocalized(buttons, TEXT_LOGIN);
|
||||||
gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
|
gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
|
||||||
gridData.verticalIndent = 10;
|
gridData.verticalIndent = 10;
|
||||||
loginButton.setLayoutData(gridData);
|
loginButton.setLayoutData(gridData);
|
||||||
|
@ -127,12 +136,17 @@ public class LoginPage implements TemplateComposer {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.registeringEnabled) {
|
if (this.registeringEnabled) {
|
||||||
final Button registerButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.register");
|
final Button registerButton = this.widgetFactory.buttonLocalized(buttons, TEXT_REGISTER);
|
||||||
gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
|
gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
|
||||||
gridData.verticalIndent = 10;
|
gridData.verticalIndent = 10;
|
||||||
registerButton.setLayoutData(gridData);
|
registerButton.setLayoutData(gridData);
|
||||||
registerButton.addListener(SWT.Selection, event -> pageContext.forwardToPage(this.defaultRegisterPage));
|
registerButton.addListener(SWT.Selection, event -> pageContext.forwardToPage(this.defaultRegisterPage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComposerService.traversePageTree(
|
||||||
|
parent,
|
||||||
|
comp -> comp instanceof Control,
|
||||||
|
comp -> comp.getAdapter(IControlAdapter.class).setTabIndex(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void login(
|
private void login(
|
||||||
|
|
|
@ -36,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
import ch.ethz.seb.sebserver.gui.content.MonitoringRunningExam.ProctoringUpdateErrorHandler;
|
||||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||||
|
@ -269,11 +270,14 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
|
|
||||||
final Supplier<EntityTable<ClientNotification>> notificationTableSupplier = _notificationTableSupplier;
|
final Supplier<EntityTable<ClientNotification>> notificationTableSupplier = _notificationTableSupplier;
|
||||||
// server push update
|
// server push update
|
||||||
|
final ProctoringUpdateErrorHandler proctoringUpdateErrorHandler =
|
||||||
|
new ProctoringUpdateErrorHandler(this.pageService, pageContext);
|
||||||
|
|
||||||
this.serverPushService.runServerPush(
|
this.serverPushService.runServerPush(
|
||||||
new ServerPushContext(
|
new ServerPushContext(
|
||||||
content,
|
content,
|
||||||
Utils.truePredicate(),
|
Utils.truePredicate(),
|
||||||
MonitoringRunningExam.createServerPushUpdateErrorHandler(this.pageService, pageContext)),
|
proctoringUpdateErrorHandler),
|
||||||
this.pollInterval,
|
this.pollInterval,
|
||||||
context -> clientConnectionDetails.updateData(),
|
context -> clientConnectionDetails.updateData(),
|
||||||
context -> clientConnectionDetails.updateGUI(notificationTableSupplier, pageContext));
|
context -> clientConnectionDetails.updateGUI(notificationTableSupplier, pageContext));
|
||||||
|
@ -365,7 +369,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
})
|
})
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) &&
|
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) &&
|
||||||
connectionData.clientConnection.status.indicatorActiveStatus);
|
connectionData.clientConnection.status.clientActiveStatus);
|
||||||
|
|
||||||
if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) {
|
if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) {
|
||||||
final ProctoringServiceSettings proctoringSettings = restService
|
final ProctoringServiceSettings proctoringSettings = restService
|
||||||
|
|
|
@ -15,6 +15,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
|
@ -43,6 +44,8 @@ public class MonitoringExamSearchPopup {
|
||||||
new LocTextKey("sebserver.monitoring.search.list.name");
|
new LocTextKey("sebserver.monitoring.search.list.name");
|
||||||
private static final LocTextKey TABLE_COLUMN_IP_ADDRESS =
|
private static final LocTextKey TABLE_COLUMN_IP_ADDRESS =
|
||||||
new LocTextKey("sebserver.monitoring.search.list.ip");
|
new LocTextKey("sebserver.monitoring.search.list.ip");
|
||||||
|
private static final LocTextKey TABLE_COLUMN_STATUS =
|
||||||
|
new LocTextKey("sebserver.monitoring.search.list.status");
|
||||||
|
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
|
|
||||||
|
@ -50,9 +53,16 @@ public class MonitoringExamSearchPopup {
|
||||||
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID);
|
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID);
|
||||||
private final TableFilterAttribute ipFilter =
|
private final TableFilterAttribute ipFilter =
|
||||||
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_IP_STRING);
|
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_IP_STRING);
|
||||||
|
private final TableFilterAttribute statusFilter;
|
||||||
|
|
||||||
protected MonitoringExamSearchPopup(final PageService pageService) {
|
protected MonitoringExamSearchPopup(final PageService pageService) {
|
||||||
this.pageService = pageService;
|
this.pageService = pageService;
|
||||||
|
|
||||||
|
this.statusFilter = new TableFilterAttribute(
|
||||||
|
CriteriaType.SINGLE_SELECTION,
|
||||||
|
ClientConnection.FILTER_ATTR_STATUS,
|
||||||
|
ConnectionStatus.ACTIVE.name(),
|
||||||
|
pageService.getResourceService()::localizedClientConnectionStatusResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void show(final PageContext pageContext) {
|
public void show(final PageContext pageContext) {
|
||||||
|
@ -60,6 +70,7 @@ public class MonitoringExamSearchPopup {
|
||||||
pageContext.getParent().getShell(),
|
pageContext.getParent().getShell(),
|
||||||
this.pageService.getWidgetFactory());
|
this.pageService.getWidgetFactory());
|
||||||
dialog.setLargeDialogWidth();
|
dialog.setLargeDialogWidth();
|
||||||
|
dialog.setDialogHeight(380);
|
||||||
dialog.open(
|
dialog.open(
|
||||||
TITLE_TEXT_KEY,
|
TITLE_TEXT_KEY,
|
||||||
pageContext,
|
pageContext,
|
||||||
|
@ -90,6 +101,12 @@ public class MonitoringExamSearchPopup {
|
||||||
ClientConnection::getClientAddress)
|
ClientConnection::getClientAddress)
|
||||||
.withFilter(this.ipFilter))
|
.withFilter(this.ipFilter))
|
||||||
|
|
||||||
|
.withColumn(new ColumnDefinition<>(
|
||||||
|
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||||
|
TABLE_COLUMN_STATUS,
|
||||||
|
ClientConnection::getStatus)
|
||||||
|
.withFilter(this.statusFilter))
|
||||||
|
|
||||||
.withDefaultAction(t -> actionBuilder
|
.withDefaultAction(t -> actionBuilder
|
||||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||||
.withParentEntityKey(examKey)
|
.withParentEntityKey(examKey)
|
||||||
|
|
|
@ -149,13 +149,22 @@ public class MonitoringRunningExam implements TemplateComposer {
|
||||||
restService.getBuilder(GetClientConnectionDataList.class)
|
restService.getBuilder(GetClientConnectionDataList.class)
|
||||||
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId());
|
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId());
|
||||||
|
|
||||||
|
final ProctoringUpdateErrorHandler proctoringUpdateErrorHandler =
|
||||||
|
new ProctoringUpdateErrorHandler(this.pageService, pageContext);
|
||||||
|
|
||||||
|
final ServerPushContext pushContext = new ServerPushContext(
|
||||||
|
content,
|
||||||
|
Utils.truePredicate(),
|
||||||
|
proctoringUpdateErrorHandler);
|
||||||
|
|
||||||
final ClientConnectionTable clientTable = new ClientConnectionTable(
|
final ClientConnectionTable clientTable = new ClientConnectionTable(
|
||||||
this.pageService,
|
this.pageService,
|
||||||
tablePane,
|
tablePane,
|
||||||
this.asyncRunner,
|
this.asyncRunner,
|
||||||
exam,
|
exam,
|
||||||
indicators,
|
indicators,
|
||||||
restCall);
|
restCall,
|
||||||
|
pushContext);
|
||||||
|
|
||||||
clientTable
|
clientTable
|
||||||
.withDefaultAction(
|
.withDefaultAction(
|
||||||
|
@ -172,10 +181,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
||||||
ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM));
|
ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM));
|
||||||
|
|
||||||
this.serverPushService.runServerPush(
|
this.serverPushService.runServerPush(
|
||||||
new ServerPushContext(
|
pushContext,
|
||||||
content,
|
|
||||||
Utils.truePredicate(),
|
|
||||||
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
|
|
||||||
this.pollInterval,
|
this.pollInterval,
|
||||||
context -> clientTable.updateValues(),
|
context -> clientTable.updateValues(),
|
||||||
updateTableGUI(clientTable));
|
updateTableGUI(clientTable));
|
||||||
|
@ -281,18 +287,26 @@ public class MonitoringRunningExam implements TemplateComposer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ProctoringUpdateErrorHandler proctoringUpdateErrorHandler =
|
||||||
|
new ProctoringUpdateErrorHandler(this.pageService, pageContext);
|
||||||
|
|
||||||
|
final ServerPushContext pushContext = new ServerPushContext(
|
||||||
|
parent,
|
||||||
|
Utils.truePredicate(),
|
||||||
|
proctoringUpdateErrorHandler);
|
||||||
|
|
||||||
this.monitoringProctoringService.initCollectingRoomActions(
|
this.monitoringProctoringService.initCollectingRoomActions(
|
||||||
|
pushContext,
|
||||||
pageContext,
|
pageContext,
|
||||||
actionBuilder,
|
actionBuilder,
|
||||||
proctoringSettings,
|
proctoringSettings,
|
||||||
proctoringGUIService);
|
proctoringGUIService);
|
||||||
|
|
||||||
this.serverPushService.runServerPush(
|
this.serverPushService.runServerPush(
|
||||||
new ServerPushContext(
|
pushContext,
|
||||||
parent,
|
|
||||||
Utils.truePredicate(),
|
|
||||||
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
|
|
||||||
this.proctoringRoomUpdateInterval,
|
this.proctoringRoomUpdateInterval,
|
||||||
context -> this.monitoringProctoringService.updateCollectingRoomActions(
|
context -> this.monitoringProctoringService.updateCollectingRoomActions(
|
||||||
|
context,
|
||||||
pageContext,
|
pageContext,
|
||||||
actionBuilder,
|
actionBuilder,
|
||||||
proctoringSettings,
|
proctoringSettings,
|
||||||
|
@ -422,7 +436,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
||||||
|
|
||||||
private Set<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) {
|
private Set<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) {
|
||||||
final Set<String> connectionTokens = clientTable.getConnectionTokens(
|
final Set<String> connectionTokens = clientTable.getConnectionTokens(
|
||||||
cc -> cc.status.indicatorActiveStatus,
|
cc -> cc.status.clientActiveStatus,
|
||||||
true);
|
true);
|
||||||
if (connectionTokens == null || connectionTokens.isEmpty()) {
|
if (connectionTokens == null || connectionTokens.isEmpty()) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
|
@ -480,30 +494,53 @@ public class MonitoringRunningExam implements TemplateComposer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Function<Exception, Boolean> createServerPushUpdateErrorHandler(
|
static final class ProctoringUpdateErrorHandler implements Function<Exception, Boolean> {
|
||||||
final PageService pageService,
|
|
||||||
final PageContext pageContext) {
|
|
||||||
|
|
||||||
return error -> {
|
private final PageService pageService;
|
||||||
log.error("Fialed to update server push: {}", error.getMessage());
|
private final PageContext pageContext;
|
||||||
|
|
||||||
|
private int errors = 0;
|
||||||
|
|
||||||
|
public ProctoringUpdateErrorHandler(
|
||||||
|
final PageService pageService,
|
||||||
|
final PageContext pageContext) {
|
||||||
|
|
||||||
|
this.pageService = pageService;
|
||||||
|
this.pageContext = pageContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkUserSession() {
|
||||||
try {
|
try {
|
||||||
pageService.getCurrentUser().get();
|
this.pageService.getCurrentUser().get();
|
||||||
|
return true;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to verify current user after server push error: {}", e.getMessage());
|
try {
|
||||||
log.info("Force logout and session cleanup...");
|
this.pageContext.forwardToLoginPage();
|
||||||
pageContext.forwardToLoginPage();
|
final MessageBox logoutSuccess = new Message(
|
||||||
final MessageBox logoutSuccess = new Message(
|
this.pageContext.getShell(),
|
||||||
pageContext.getShell(),
|
this.pageService.getI18nSupport().getText("sebserver.logout"),
|
||||||
pageService.getI18nSupport().getText("sebserver.logout"),
|
Utils.formatLineBreaks(
|
||||||
Utils.formatLineBreaks(
|
this.pageService.getI18nSupport()
|
||||||
pageService.getI18nSupport().getText("sebserver.logout.invalid-session.message")),
|
.getText("sebserver.logout.invalid-session.message")),
|
||||||
SWT.ICON_INFORMATION,
|
SWT.ICON_INFORMATION,
|
||||||
pageService.getI18nSupport());
|
this.pageService.getI18nSupport());
|
||||||
logoutSuccess.open(null);
|
logoutSuccess.open(null);
|
||||||
|
} catch (final Exception ee) {
|
||||||
|
log.warn("Unable to auto-logout: ", ee.getMessage());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
};
|
|
||||||
|
@Override
|
||||||
|
public Boolean apply(final Exception error) {
|
||||||
|
this.errors++;
|
||||||
|
log.error("Failed to update server push: {}", error.getMessage());
|
||||||
|
if (this.errors > 5) {
|
||||||
|
checkUserSession();
|
||||||
|
}
|
||||||
|
return this.errors > 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,21 +8,28 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.content;
|
package ch.ethz.seb.sebserver.gui.content;
|
||||||
|
|
||||||
import org.eclipse.swt.SWT;
|
import java.util.ArrayList;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import java.util.List;
|
||||||
import org.eclipse.swt.widgets.Label;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRoomConnections;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRoomConnections;
|
||||||
|
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||||
|
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@ -31,6 +38,10 @@ public class ProctorRoomConnectionsPopup {
|
||||||
|
|
||||||
private static final LocTextKey TITLE_TEXT_KEY =
|
private static final LocTextKey TITLE_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.monitoring.exam.proctoring.room.connections.title");
|
new LocTextKey("sebserver.monitoring.exam.proctoring.room.connections.title");
|
||||||
|
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||||
|
new LocTextKey("sebserver.monitoring.search.list.empty");
|
||||||
|
private static final LocTextKey TABLE_COLUMN_NAME =
|
||||||
|
new LocTextKey("sebserver.monitoring.search.list.name");
|
||||||
|
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
|
|
||||||
|
@ -43,29 +54,60 @@ public class ProctorRoomConnectionsPopup {
|
||||||
pageContext.getParent().getShell(),
|
pageContext.getParent().getShell(),
|
||||||
this.pageService.getWidgetFactory());
|
this.pageService.getWidgetFactory());
|
||||||
dialog.setLargeDialogWidth();
|
dialog.setLargeDialogWidth();
|
||||||
|
dialog.setDialogHeight(380);
|
||||||
dialog.open(
|
dialog.open(
|
||||||
new LocTextKey(TITLE_TEXT_KEY.name, roomSubject),
|
new LocTextKey(TITLE_TEXT_KEY.name, roomSubject),
|
||||||
pageContext,
|
pageContext,
|
||||||
this::compose);
|
c -> this.compose(c, dialog));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void compose(final PageContext pageContext) {
|
private void compose(final PageContext pageContext, final ModalInputDialog<Void> dialog) {
|
||||||
final Composite parent = pageContext.getParent();
|
|
||||||
final Composite grid = this.pageService.getWidgetFactory().createPopupScrollComposite(parent);
|
|
||||||
final EntityKey entityKey = pageContext.getEntityKey();
|
final EntityKey entityKey = pageContext.getEntityKey();
|
||||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||||
|
|
||||||
this.pageService.getRestService()
|
final PageActionBuilder actionBuilder = this.pageService
|
||||||
|
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||||
|
|
||||||
|
final List<ClientConnection> connections = new ArrayList<>(this.pageService.getRestService()
|
||||||
.getBuilder(GetCollectingRoomConnections.class)
|
.getBuilder(GetCollectingRoomConnections.class)
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||||
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, entityKey.modelId)
|
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, entityKey.modelId)
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow()
|
.getOrThrow());
|
||||||
.stream()
|
|
||||||
.forEach(connection -> {
|
this.pageService.staticListTableBuilder(connections, EntityType.CLIENT_CONNECTION)
|
||||||
final Label label = new Label(grid, SWT.NONE);
|
|
||||||
label.setText(connection.userSessionId);
|
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||||
});
|
.withPaging(10)
|
||||||
|
|
||||||
|
.withColumn(new ColumnDefinition<>(
|
||||||
|
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||||
|
TABLE_COLUMN_NAME,
|
||||||
|
ClientConnection::getUserSessionId))
|
||||||
|
|
||||||
|
.withDefaultAction(t -> actionBuilder
|
||||||
|
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||||
|
.withParentEntityKey(parentEntityKey)
|
||||||
|
.withExec(action -> showClientConnection(action, dialog, t))
|
||||||
|
.create())
|
||||||
|
|
||||||
|
.compose(pageContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PageAction showClientConnection(
|
||||||
|
final PageAction pageAction,
|
||||||
|
final ModalInputDialog<Void> dialog,
|
||||||
|
final EntityTable<ClientConnection> table) {
|
||||||
|
|
||||||
|
final ClientConnection singleSelectedROWData = table.getSingleSelectedROWData();
|
||||||
|
dialog.close();
|
||||||
|
return pageAction
|
||||||
|
.withEntityKey(new EntityKey(
|
||||||
|
singleSelectedROWData.id,
|
||||||
|
EntityType.CLIENT_CONNECTION))
|
||||||
|
.withAttribute(
|
||||||
|
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
||||||
|
singleSelectedROWData.getConnectionToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,10 +119,10 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final Text textInput = (this.isNumber)
|
final Text textInput = (this.isNumber)
|
||||||
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly)
|
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly, this.label)
|
||||||
: (this.isArea)
|
: (this.isArea)
|
||||||
? builder.widgetFactory.textAreaInput(fieldGrid, readonly)
|
? builder.widgetFactory.textAreaInput(fieldGrid, readonly, this.label)
|
||||||
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly);
|
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly, this.label);
|
||||||
|
|
||||||
if (builder.pageService.getFormTooltipMode() == PageService.FormTooltipMode.INPUT) {
|
if (builder.pageService.getFormTooltipMode() == PageService.FormTooltipMode.INPUT) {
|
||||||
builder.pageService.getPolyglotPageService().injectI18nTooltip(
|
builder.pageService.getPolyglotPageService().injectI18nTooltip(
|
||||||
|
|
|
@ -567,6 +567,14 @@ public class ResourceService {
|
||||||
.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + name, name);
|
.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + name, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Tuple<String>> localizedClientConnectionStatusResources() {
|
||||||
|
return Arrays.stream(ConnectionStatus.values())
|
||||||
|
.map(type -> new Tuple<>(type.name(),
|
||||||
|
this.i18nSupport.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + type.name())))
|
||||||
|
.sorted(RESOURCE_COMPARATOR)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public String localizedExamTypeName(final ExamConfigurationMap examMap) {
|
public String localizedExamTypeName(final ExamConfigurationMap examMap) {
|
||||||
if (examMap.examType == null) {
|
if (examMap.examType == null) {
|
||||||
return Constants.EMPTY_NOTE;
|
return Constants.EMPTY_NOTE;
|
||||||
|
|
|
@ -93,12 +93,16 @@ public abstract class AbstractProctoringView implements RemoteProctoringView {
|
||||||
final BroadcastActionState state =
|
final BroadcastActionState state =
|
||||||
(BroadcastActionState) videoAction.getData(BroadcastActionState.KEY_NAME);
|
(BroadcastActionState) videoAction.getData(BroadcastActionState.KEY_NAME);
|
||||||
|
|
||||||
this.pageService.getPolyglotPageService().injectI18n(
|
if (audioAction != null) {
|
||||||
audioAction,
|
this.pageService.getPolyglotPageService().injectI18n(
|
||||||
state.video ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
|
audioAction,
|
||||||
this.pageService.getPolyglotPageService().injectI18n(
|
state.video ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
|
||||||
videoAction,
|
}
|
||||||
state.video ? BROADCAST_VIDEO_ON_TEXT_KEY : BROADCAST_VIDEO_OFF_TEXT_KEY);
|
if (videoAction != null) {
|
||||||
|
this.pageService.getPolyglotPageService().injectI18n(
|
||||||
|
videoAction,
|
||||||
|
state.video ? BROADCAST_VIDEO_ON_TEXT_KEY : BROADCAST_VIDEO_OFF_TEXT_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
state.video = !state.video;
|
state.video = !state.video;
|
||||||
state.audio = state.video;
|
state.audio = state.video;
|
||||||
|
|
|
@ -46,6 +46,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.Message;
|
import ch.ethz.seb.sebserver.gui.widget.Message;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.AriaRole;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -285,6 +286,8 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
log.error("Invalid markup for 'Imprint'", e);
|
log.error("Invalid markup for 'Imprint'", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WidgetFactory.setARIARole(imprint, AriaRole.link);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNoneBlank(i18nSupport.getText(ABOUT_TEXT_KEY, ""))) {
|
if (StringUtils.isNoneBlank(i18nSupport.getText(ABOUT_TEXT_KEY, ""))) {
|
||||||
final Label about = this.widgetFactory.labelLocalized(
|
final Label about = this.widgetFactory.labelLocalized(
|
||||||
|
@ -299,6 +302,8 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
log.error("Invalid markup for 'About'", e);
|
log.error("Invalid markup for 'About'", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WidgetFactory.setARIARole(about, AriaRole.link);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNoneBlank(i18nSupport.getText(HELP_TEXT_KEY, ""))) {
|
if (StringUtils.isNoneBlank(i18nSupport.getText(HELP_TEXT_KEY, ""))) {
|
||||||
final Label help = this.widgetFactory.labelLocalized(
|
final Label help = this.widgetFactory.labelLocalized(
|
||||||
|
@ -318,6 +323,7 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WidgetFactory.setARIARole(help, AriaRole.link);
|
||||||
}
|
}
|
||||||
this.widgetFactory.labelLocalized(
|
this.widgetFactory.labelLocalized(
|
||||||
footerRight,
|
footerRight,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.eclipse.swt.layout.RowData;
|
||||||
import org.eclipse.swt.layout.RowLayout;
|
import org.eclipse.swt.layout.RowLayout;
|
||||||
import org.eclipse.swt.widgets.Button;
|
import org.eclipse.swt.widgets.Button;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.Label;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -78,6 +79,11 @@ public class JitsiMeetProctoringView extends AbstractProctoringView {
|
||||||
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||||
content.setLayoutData(headerCell);
|
content.setLayoutData(headerCell);
|
||||||
|
|
||||||
|
final Label title = this.pageService
|
||||||
|
.getWidgetFactory()
|
||||||
|
.label(content, proctoringWindowData.connectionData.subject);
|
||||||
|
title.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, true));
|
||||||
|
|
||||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||||
|
|
||||||
final String url = this.guiServiceInfo
|
final String url = this.guiServiceInfo
|
||||||
|
|
|
@ -49,6 +49,7 @@ public class ModalInputDialog<T> extends Dialog {
|
||||||
private int dialogWidth = DEFAULT_DIALOG_WIDTH;
|
private int dialogWidth = DEFAULT_DIALOG_WIDTH;
|
||||||
private int dialogHeight = DEFAULT_DIALOG_HEIGHT;
|
private int dialogHeight = DEFAULT_DIALOG_HEIGHT;
|
||||||
private int buttonWidth = DEFAULT_DIALOG_BUTTON_WIDTH;
|
private int buttonWidth = DEFAULT_DIALOG_BUTTON_WIDTH;
|
||||||
|
private boolean forceHeight = false;
|
||||||
|
|
||||||
public ModalInputDialog(
|
public ModalInputDialog(
|
||||||
final Shell parent,
|
final Shell parent,
|
||||||
|
@ -75,6 +76,7 @@ public class ModalInputDialog<T> extends Dialog {
|
||||||
|
|
||||||
public ModalInputDialog<T> setDialogHeight(final int dialogHeight) {
|
public ModalInputDialog<T> setDialogHeight(final int dialogHeight) {
|
||||||
this.dialogHeight = dialogHeight;
|
this.dialogHeight = dialogHeight;
|
||||||
|
this.forceHeight = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +217,9 @@ public class ModalInputDialog<T> extends Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int calcDialogHeight(final Composite main) {
|
private int calcDialogHeight(final Composite main) {
|
||||||
|
if (this.forceHeight) {
|
||||||
|
return this.dialogHeight;
|
||||||
|
}
|
||||||
final int actualHeight = main.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
|
final int actualHeight = main.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
|
||||||
final int displayHeight = main.getDisplay().getClientArea().height;
|
final int displayHeight = main.getDisplay().getClientArea().height;
|
||||||
final int availableHeight = (displayHeight < actualHeight + 100)
|
final int availableHeight = (displayHeight < actualHeight + 100)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.eclipse.swt.layout.RowData;
|
||||||
import org.eclipse.swt.layout.RowLayout;
|
import org.eclipse.swt.layout.RowLayout;
|
||||||
import org.eclipse.swt.widgets.Button;
|
import org.eclipse.swt.widgets.Button;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.Label;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -78,6 +79,11 @@ public class ZoomProctoringView extends AbstractProctoringView {
|
||||||
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||||
content.setLayoutData(headerCell);
|
content.setLayoutData(headerCell);
|
||||||
|
|
||||||
|
final Label title = this.pageService
|
||||||
|
.getWidgetFactory()
|
||||||
|
.label(content, proctoringWindowData.connectionData.subject);
|
||||||
|
title.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
|
||||||
|
|
||||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||||
|
|
||||||
final String url = this.guiServiceInfo
|
final String url = this.guiServiceInfo
|
||||||
|
@ -111,13 +117,13 @@ public class ZoomProctoringView extends AbstractProctoringView {
|
||||||
|
|
||||||
final BroadcastActionState broadcastActionState = new BroadcastActionState();
|
final BroadcastActionState broadcastActionState = new BroadcastActionState();
|
||||||
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.BROADCAST)) {
|
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.BROADCAST)) {
|
||||||
final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY);
|
// final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY);
|
||||||
broadcastAudioAction.setLayoutData(new RowData());
|
// broadcastAudioAction.setLayoutData(new RowData());
|
||||||
broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio(
|
// broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio(
|
||||||
proctoringWindowData.examId,
|
// proctoringWindowData.examId,
|
||||||
proctoringWindowData.connectionData.roomName,
|
// proctoringWindowData.connectionData.roomName,
|
||||||
broadcastAudioAction));
|
// broadcastAudioAction));
|
||||||
broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
// broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
||||||
|
|
||||||
final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY);
|
final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY);
|
||||||
broadcastVideoAction.setLayoutData(new RowData());
|
broadcastVideoAction.setLayoutData(new RowData());
|
||||||
|
@ -125,7 +131,7 @@ public class ZoomProctoringView extends AbstractProctoringView {
|
||||||
proctoringWindowData.examId,
|
proctoringWindowData.examId,
|
||||||
proctoringWindowData.connectionData.roomName,
|
proctoringWindowData.connectionData.roomName,
|
||||||
broadcastVideoAction,
|
broadcastVideoAction,
|
||||||
broadcastAudioAction));
|
null));
|
||||||
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
||||||
}
|
}
|
||||||
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.ENABLE_CHAT)) {
|
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.ENABLE_CHAT)) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ public final class ServerPushContext {
|
||||||
|
|
||||||
public final Composite anchor;
|
public final Composite anchor;
|
||||||
public final Predicate<ServerPushContext> runAgain;
|
public final Predicate<ServerPushContext> runAgain;
|
||||||
public final Function<Exception, Boolean> errorHandler;
|
final Function<Exception, Boolean> errorHandler;
|
||||||
boolean internalStop = false;
|
boolean internalStop = false;
|
||||||
|
|
||||||
public ServerPushContext(
|
public ServerPushContext(
|
||||||
|
@ -36,6 +36,12 @@ public final class ServerPushContext {
|
||||||
this.runAgain = runAgain;
|
this.runAgain = runAgain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reportError(final Exception error) {
|
||||||
|
if (this.errorHandler != null) {
|
||||||
|
this.internalStop = this.errorHandler.apply(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean runAgain() {
|
public boolean runAgain() {
|
||||||
return !this.internalStop && this.runAgain.test(this);
|
return !this.internalStop && this.runAgain.test(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,7 +189,7 @@ public class ClientConnectionDetails {
|
||||||
final double value = indValue.getValue();
|
final double value = indValue.getValue();
|
||||||
final String displayValue = IndicatorValue.getDisplayValue(indValue);
|
final String displayValue = IndicatorValue.getDisplayValue(indValue);
|
||||||
|
|
||||||
if (!this.connectionData.clientConnection.status.indicatorActiveStatus) {
|
if (!this.connectionData.clientConnection.status.clientActiveStatus) {
|
||||||
|
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
indData.indicator.name,
|
indData.indicator.name,
|
||||||
|
|
|
@ -61,6 +61,7 @@ import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
|
||||||
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
||||||
|
@ -95,6 +96,8 @@ public final class ClientConnectionTable {
|
||||||
private final AsyncRunner asyncRunner;
|
private final AsyncRunner asyncRunner;
|
||||||
private final Exam exam;
|
private final Exam exam;
|
||||||
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
|
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
|
||||||
|
private final ServerPushContext pushConext;
|
||||||
|
|
||||||
private final Map<Long, IndicatorData> indicatorMapping;
|
private final Map<Long, IndicatorData> indicatorMapping;
|
||||||
private final Table table;
|
private final Table table;
|
||||||
private final ColorData colorData;
|
private final ColorData colorData;
|
||||||
|
@ -116,18 +119,22 @@ public final class ClientConnectionTable {
|
||||||
private boolean forceUpdateAll = false;
|
private boolean forceUpdateAll = false;
|
||||||
private boolean updateInProgress = false;
|
private boolean updateInProgress = false;
|
||||||
|
|
||||||
|
//private int updateErrors = 0;
|
||||||
|
|
||||||
public ClientConnectionTable(
|
public ClientConnectionTable(
|
||||||
final PageService pageService,
|
final PageService pageService,
|
||||||
final Composite tableRoot,
|
final Composite tableRoot,
|
||||||
final AsyncRunner asyncRunner,
|
final AsyncRunner asyncRunner,
|
||||||
final Exam exam,
|
final Exam exam,
|
||||||
final Collection<Indicator> indicators,
|
final Collection<Indicator> indicators,
|
||||||
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder) {
|
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder,
|
||||||
|
final ServerPushContext pushConext) {
|
||||||
|
|
||||||
this.pageService = pageService;
|
this.pageService = pageService;
|
||||||
this.asyncRunner = asyncRunner;
|
this.asyncRunner = asyncRunner;
|
||||||
this.exam = exam;
|
this.exam = exam;
|
||||||
this.restCallBuilder = restCallBuilder;
|
this.restCallBuilder = restCallBuilder;
|
||||||
|
this.pushConext = pushConext;
|
||||||
|
|
||||||
final WidgetFactory widgetFactory = pageService.getWidgetFactory();
|
final WidgetFactory widgetFactory = pageService.getWidgetFactory();
|
||||||
final ResourceService resourceService = pageService.getResourceService();
|
final ResourceService resourceService = pageService.getResourceService();
|
||||||
|
@ -188,6 +195,10 @@ public final class ClientConnectionTable {
|
||||||
this.table.layout();
|
this.table.layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public int getUpdateErrors() {
|
||||||
|
// return this.updateErrors;
|
||||||
|
// }
|
||||||
|
|
||||||
public WidgetFactory getWidgetFactory() {
|
public WidgetFactory getWidgetFactory() {
|
||||||
return this.pageService.getWidgetFactory();
|
return this.pageService.getWidgetFactory();
|
||||||
}
|
}
|
||||||
|
@ -316,48 +327,58 @@ public final class ClientConnectionTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateInProgress = true;
|
this.updateInProgress = true;
|
||||||
this.asyncRunner.runAsync(this::updateValuesAsync);
|
final boolean needsSync = this.tableMapping != null &&
|
||||||
|
this.table != null &&
|
||||||
|
this.tableMapping.size() != this.table.getItemCount();
|
||||||
|
this.asyncRunner.runAsync(() -> updateValuesAsync(needsSync));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateValuesAsync() {
|
private void updateValuesAsync(final boolean needsSync) {
|
||||||
if (this.statusFilterChanged || this.forceUpdateAll) {
|
|
||||||
this.toDelete.clear();
|
try {
|
||||||
this.toDelete.addAll(this.tableMapping.keySet());
|
|
||||||
}
|
if (this.statusFilterChanged || this.forceUpdateAll || needsSync) {
|
||||||
this.restCallBuilder
|
this.toDelete.clear();
|
||||||
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
|
this.toDelete.addAll(this.tableMapping.keySet());
|
||||||
.call()
|
}
|
||||||
.get(error -> {
|
this.restCallBuilder
|
||||||
log.error("Unexpected error while trying to get client connection table data: ", error);
|
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
|
||||||
recoverFromDisposedRestTemplate(error);
|
.call()
|
||||||
return Collections.emptyList();
|
.get(error -> {
|
||||||
})
|
recoverFromDisposedRestTemplate(error);
|
||||||
.forEach(data -> {
|
this.pushConext.reportError(error);
|
||||||
final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
|
return Collections.emptyList();
|
||||||
data.getConnectionId(),
|
})
|
||||||
UpdatableTableItem::new);
|
.forEach(data -> {
|
||||||
tableItem.push(data);
|
final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
|
||||||
if (this.statusFilterChanged || this.forceUpdateAll) {
|
data.getConnectionId(),
|
||||||
this.toDelete.remove(data.getConnectionId());
|
UpdatableTableItem::new);
|
||||||
|
tableItem.push(data);
|
||||||
|
if (this.statusFilterChanged || this.forceUpdateAll || needsSync) {
|
||||||
|
this.toDelete.remove(data.getConnectionId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.toDelete.isEmpty()) {
|
||||||
|
this.toDelete.forEach(id -> {
|
||||||
|
final UpdatableTableItem item = this.tableMapping.remove(id);
|
||||||
|
if (item != null) {
|
||||||
|
final List<Long> list = this.sessionIds.get(item.connectionData.clientConnection.userSessionId);
|
||||||
|
if (list != null) {
|
||||||
|
list.remove(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.statusFilterChanged = false;
|
||||||
|
this.toDelete.clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.toDelete.isEmpty()) {
|
this.forceUpdateAll = false;
|
||||||
this.toDelete.forEach(id -> {
|
this.updateInProgress = false;
|
||||||
final UpdatableTableItem item = this.tableMapping.remove(id);
|
|
||||||
if (item != null) {
|
} catch (final Exception e) {
|
||||||
final List<Long> list = this.sessionIds.get(item.connectionData.clientConnection.userSessionId);
|
this.pushConext.reportError(e);
|
||||||
if (list != null) {
|
|
||||||
list.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.statusFilterChanged = false;
|
|
||||||
this.toDelete.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.forceUpdateAll = false;
|
|
||||||
this.updateInProgress = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateGUI() {
|
public void updateGUI() {
|
||||||
|
@ -576,7 +597,7 @@ public final class ClientConnectionTable {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.connectionData.clientConnection.status.indicatorActiveStatus) {
|
if (!this.connectionData.clientConnection.status.clientActiveStatus) {
|
||||||
final String value = (indicatorData.indicator.type.showOnlyInActiveState)
|
final String value = (indicatorData.indicator.type.showOnlyInActiveState)
|
||||||
? Constants.EMPTY_NOTE
|
? Constants.EMPTY_NOTE
|
||||||
: IndicatorValue.getDisplayValue(indicatorValue);
|
: IndicatorValue.getDisplayValue(indicatorValue);
|
||||||
|
|
|
@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.gui.service.session.proctoring;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.eclipse.rap.rwt.RWT;
|
import org.eclipse.rap.rwt.RWT;
|
||||||
|
@ -47,7 +46,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent;
|
import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
|
||||||
|
@ -80,6 +79,7 @@ public class MonitoringProctoringService {
|
||||||
static final String OPEN_ROOM_SCRIPT =
|
static final String OPEN_ROOM_SCRIPT =
|
||||||
"try {\n" +
|
"try {\n" +
|
||||||
"var existingWin = window.open('', '%s', 'height=%s,width=%s,location=no,scrollbars=yes,status=no,menubar=0,toolbar=no,titlebar=no,dialog=no');\n" +
|
"var existingWin = window.open('', '%s', 'height=%s,width=%s,location=no,scrollbars=yes,status=no,menubar=0,toolbar=no,titlebar=no,dialog=no');\n" +
|
||||||
|
"existingWin.document.title = '%s';\n" +
|
||||||
"if(existingWin.location.href === 'about:blank'){\n" +
|
"if(existingWin.location.href === 'about:blank'){\n" +
|
||||||
" existingWin.location.href = '%s%s';\n" +
|
" existingWin.location.href = '%s%s';\n" +
|
||||||
" existingWin.focus();\n" +
|
" existingWin.focus();\n" +
|
||||||
|
@ -152,6 +152,7 @@ public class MonitoringProctoringService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initCollectingRoomActions(
|
public void initCollectingRoomActions(
|
||||||
|
final ServerPushContext pushContext,
|
||||||
final PageContext pageContext,
|
final PageContext pageContext,
|
||||||
final PageActionBuilder actionBuilder,
|
final PageActionBuilder actionBuilder,
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
|
@ -159,6 +160,7 @@ public class MonitoringProctoringService {
|
||||||
|
|
||||||
proctoringGUIService.clearCollectingRoomActionState();
|
proctoringGUIService.clearCollectingRoomActionState();
|
||||||
updateCollectingRoomActions(
|
updateCollectingRoomActions(
|
||||||
|
pushContext,
|
||||||
pageContext,
|
pageContext,
|
||||||
actionBuilder,
|
actionBuilder,
|
||||||
proctoringSettings,
|
proctoringSettings,
|
||||||
|
@ -166,6 +168,7 @@ public class MonitoringProctoringService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateCollectingRoomActions(
|
public void updateCollectingRoomActions(
|
||||||
|
final ServerPushContext pushContext,
|
||||||
final PageContext pageContext,
|
final PageContext pageContext,
|
||||||
final PageActionBuilder actionBuilder,
|
final PageActionBuilder actionBuilder,
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
|
@ -179,7 +182,7 @@ public class MonitoringProctoringService {
|
||||||
.getBuilder(GetCollectingRooms.class)
|
.getBuilder(GetCollectingRooms.class)
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||||
.call()
|
.call()
|
||||||
.onError(error -> log.error("Failed to update proctoring rooms on GUI {}", error.getMessage()))
|
.onError(error -> pushContext.reportError(error))
|
||||||
.getOr(Collections.emptyList())
|
.getOr(Collections.emptyList())
|
||||||
.stream()
|
.stream()
|
||||||
.forEach(room -> {
|
.forEach(room -> {
|
||||||
|
@ -198,14 +201,11 @@ public class MonitoringProctoringService {
|
||||||
final PageAction action =
|
final PageAction action =
|
||||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM)
|
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(_action -> {
|
.withExec(_action -> openExamProctoringRoom(
|
||||||
final int actualRoomSize = proctoringGUIService
|
proctoringGUIService,
|
||||||
.getActualCollectingRoomSize(room.name);
|
proctoringSettings,
|
||||||
if (actualRoomSize <= 0) {
|
room,
|
||||||
return _action;
|
_action))
|
||||||
}
|
|
||||||
return showExamProctoringRoom(proctoringSettings, room, _action);
|
|
||||||
})
|
|
||||||
.withNameAttributes(
|
.withNameAttributes(
|
||||||
room.subject,
|
room.subject,
|
||||||
room.roomSize,
|
room.roomSize,
|
||||||
|
@ -226,6 +226,7 @@ public class MonitoringProctoringService {
|
||||||
.withParentEntityKey(entityKey);
|
.withParentEntityKey(entityKey);
|
||||||
this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject);
|
this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
processProctorRoomActionActivation(
|
processProctorRoomActionActivation(
|
||||||
proctoringGUIService.getCollectingRoomActionItem(room.name),
|
proctoringGUIService.getCollectingRoomActionItem(room.name),
|
||||||
room, pageContext);
|
room, pageContext);
|
||||||
|
@ -235,60 +236,53 @@ public class MonitoringProctoringService {
|
||||||
updateTownhallButton(proctoringGUIService, pageContext);
|
updateTownhallButton(proctoringGUIService, pageContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PageAction openExamCollectionProctorScreen(
|
private PageAction openExamProctoringRoom(
|
||||||
final PageAction action,
|
final ProctoringGUIService proctoringGUIService,
|
||||||
final ClientConnectionData connectionData) {
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
|
final RemoteProctoringRoom room,
|
||||||
|
final PageAction action) {
|
||||||
|
|
||||||
try {
|
if (!proctoringGUIService.isCollectingRoomEnabled(room.name)) {
|
||||||
final String examId = action.getEntityKey().modelId;
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
final ProctoringServiceSettings proctoringSettings = this.pageService.getRestService()
|
final ProctoringRoomConnection proctoringConnectionData = this.pageService
|
||||||
.getBuilder(GetProctoringSettings.class)
|
.getRestService()
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, examId)
|
.getBuilder(GetProctorRoomConnection.class)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
|
||||||
|
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
|
||||||
|
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
|
||||||
|
.call()
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
ProctoringGUIService.setCurrentProctoringWindowData(
|
||||||
|
String.valueOf(proctoringSettings.examId),
|
||||||
|
proctoringConnectionData);
|
||||||
|
|
||||||
|
final String script = String.format(
|
||||||
|
OPEN_ROOM_SCRIPT,
|
||||||
|
room.name,
|
||||||
|
800,
|
||||||
|
1200,
|
||||||
|
room.name,
|
||||||
|
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
||||||
|
this.remoteProctoringEndpoint);
|
||||||
|
|
||||||
|
RWT.getClient()
|
||||||
|
.getService(JavaScriptExecutor.class)
|
||||||
|
.execute(script);
|
||||||
|
|
||||||
|
final boolean newWindow = this.pageService.getCurrentUser()
|
||||||
|
.getProctoringGUIService()
|
||||||
|
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
|
||||||
|
|
||||||
|
if (newWindow) {
|
||||||
|
this.pageService.getRestService()
|
||||||
|
.getBuilder(NotifyProctoringRoomOpened.class)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
|
||||||
|
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow();
|
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
|
||||||
|
|
||||||
final Optional<RemoteProctoringRoom> roomOptional =
|
|
||||||
this.pageService.getRestService().getBuilder(GetCollectingRooms.class)
|
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, examId)
|
|
||||||
.call()
|
|
||||||
.getOrThrow()
|
|
||||||
.stream()
|
|
||||||
.filter(room -> room.id.equals(connectionData.clientConnection.remoteProctoringRoomId))
|
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
if (roomOptional.isPresent()) {
|
|
||||||
final RemoteProctoringRoom room = roomOptional.get();
|
|
||||||
final ProctoringRoomConnection proctoringConnectionData = this.pageService
|
|
||||||
.getRestService()
|
|
||||||
.getBuilder(GetProctorRoomConnection.class)
|
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
|
|
||||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
|
|
||||||
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
|
|
||||||
.call()
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData);
|
|
||||||
final String script = String.format(
|
|
||||||
MonitoringProctoringService.OPEN_ROOM_SCRIPT,
|
|
||||||
room.name,
|
|
||||||
800,
|
|
||||||
1200,
|
|
||||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
|
||||||
this.remoteProctoringEndpoint);
|
|
||||||
|
|
||||||
RWT.getClient()
|
|
||||||
.getService(JavaScriptExecutor.class)
|
|
||||||
.execute(script);
|
|
||||||
|
|
||||||
this.pageService.getCurrentUser()
|
|
||||||
.getProctoringGUIService()
|
|
||||||
.registerProctoringWindow(examId, room.name, room.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("Failed to open popup for collecting room: ", e);
|
|
||||||
action.pageContext().notifyError(CLOSE_COLLECTING_ERROR, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
|
@ -329,6 +323,7 @@ public class MonitoringProctoringService {
|
||||||
connectionToken,
|
connectionToken,
|
||||||
420,
|
420,
|
||||||
640,
|
640,
|
||||||
|
connectionData.clientConnection.userSessionId,
|
||||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
||||||
this.remoteProctoringEndpoint);
|
this.remoteProctoringEndpoint);
|
||||||
javaScriptExecutor.execute(script);
|
javaScriptExecutor.execute(script);
|
||||||
|
@ -370,6 +365,7 @@ public class MonitoringProctoringService {
|
||||||
windowName,
|
windowName,
|
||||||
800,
|
800,
|
||||||
1200,
|
1200,
|
||||||
|
"Town-Hall",
|
||||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
||||||
this.remoteProctoringEndpoint);
|
this.remoteProctoringEndpoint);
|
||||||
javaScriptExecutor.execute(script);
|
javaScriptExecutor.execute(script);
|
||||||
|
@ -444,59 +440,19 @@ public class MonitoringProctoringService {
|
||||||
final PageContext pageContext) {
|
final PageContext pageContext) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
final boolean active = room.roomSize > 0 && !room.isOpen;
|
||||||
final Display display = pageContext.getRoot().getDisplay();
|
final Display display = pageContext.getRoot().getDisplay();
|
||||||
final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY);
|
final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY);
|
||||||
final Image image = room.roomSize > 0
|
final Image image = active
|
||||||
? action.definition.icon.getImage(display)
|
? action.definition.icon.getImage(display)
|
||||||
: action.definition.icon.getGreyedImage(display);
|
: action.definition.icon.getGreyedImage(display);
|
||||||
treeItem.setImage(image);
|
treeItem.setImage(image);
|
||||||
treeItem.setForeground(room.roomSize > 0 ? null : new Color(display, Constants.GREY_DISABLED));
|
treeItem.setForeground(active ? null : new Color(display, Constants.GREY_DISABLED));
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.warn("Failed to set Proctor-Room-Activation: ", e.getMessage());
|
log.warn("Failed to set Proctor-Room-Activation: ", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageAction showExamProctoringRoom(
|
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
|
||||||
final RemoteProctoringRoom room,
|
|
||||||
final PageAction action) {
|
|
||||||
|
|
||||||
final ProctoringRoomConnection proctoringConnectionData = this.pageService
|
|
||||||
.getRestService()
|
|
||||||
.getBuilder(GetProctorRoomConnection.class)
|
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
|
|
||||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
|
|
||||||
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
|
|
||||||
.call()
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
ProctoringGUIService.setCurrentProctoringWindowData(
|
|
||||||
String.valueOf(proctoringSettings.examId),
|
|
||||||
proctoringConnectionData);
|
|
||||||
|
|
||||||
final String script = String.format(
|
|
||||||
OPEN_ROOM_SCRIPT,
|
|
||||||
room.name,
|
|
||||||
800,
|
|
||||||
1200,
|
|
||||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
|
||||||
this.remoteProctoringEndpoint);
|
|
||||||
|
|
||||||
RWT.getClient()
|
|
||||||
.getService(JavaScriptExecutor.class)
|
|
||||||
.execute(script);
|
|
||||||
|
|
||||||
this.pageService.getCurrentUser()
|
|
||||||
.getProctoringGUIService()
|
|
||||||
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
|
|
||||||
|
|
||||||
this.pageService.getRestService().getBuilder(NotifyProctoringRoomOpened.class)
|
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
|
|
||||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
|
|
||||||
.call()
|
|
||||||
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
|
|
||||||
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,6 @@ public class ProctoringGUIService {
|
||||||
final RemoteProctoringRoom remoteProctoringRoom = getRemoteProctoringRoom(item);
|
final RemoteProctoringRoom remoteProctoringRoom = getRemoteProctoringRoom(item);
|
||||||
if (remoteProctoringRoom != null && remoteProctoringRoom.roomSize > 0) {
|
if (remoteProctoringRoom != null && remoteProctoringRoom.roomSize > 0) {
|
||||||
showConnectionsPopup.accept(remoteProctoringRoom);
|
showConnectionsPopup.accept(remoteProctoringRoom);
|
||||||
//this.proctorRoomConnectionsPopup.show(pc, remoteProctoringRoom.subject);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -99,12 +98,13 @@ public class ProctoringGUIService {
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getActualCollectingRoomSize(final String roomName) {
|
public boolean isCollectingRoomEnabled(final String roomName) {
|
||||||
try {
|
try {
|
||||||
return this.collectingRoomsActionState.get(roomName).a.roomSize;
|
final Pair<RemoteProctoringRoom, TreeItem> pair = this.collectingRoomsActionState.get(roomName);
|
||||||
|
return pair.a.roomSize > 0 && !pair.a.isOpen;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to get actual collecting room size for room: {} cause: ", roomName, e.getMessage());
|
log.error("Failed to get actual collecting room size for room: {} cause: ", roomName, e.getMessage());
|
||||||
return -1;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,12 +117,14 @@ public class ProctoringGUIService {
|
||||||
this.collectingRoomsActionState.clear();
|
this.collectingRoomsActionState.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerProctoringWindow(
|
public boolean registerProctoringWindow(
|
||||||
final String examId,
|
final String examId,
|
||||||
final String windowName,
|
final String windowName,
|
||||||
final String roomName) {
|
final String roomName) {
|
||||||
|
|
||||||
this.openWindows.put(windowName, new RoomData(roomName, examId));
|
return this.openWindows.putIfAbsent(
|
||||||
|
windowName,
|
||||||
|
new RoomData(roomName, examId)) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTownhallWindowName(final String examId) {
|
public String getTownhallWindowName(final String examId) {
|
||||||
|
|
|
@ -321,7 +321,9 @@ public class TableFilter<ROW> {
|
||||||
final Composite innerComposite = createInnerComposite(parent);
|
final Composite innerComposite = createInnerComposite(parent);
|
||||||
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
|
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
|
||||||
|
|
||||||
this.textInput = TableFilter.this.entityTable.widgetFactory.textInput(innerComposite);
|
this.textInput = TableFilter.this.entityTable.widgetFactory.textInput(
|
||||||
|
innerComposite,
|
||||||
|
super.attribute.columnName);
|
||||||
this.textInput.setLayoutData(gridData);
|
this.textInput.setLayoutData(gridData);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,11 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.widget;
|
package ch.ethz.seb.sebserver.gui.widget;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import java.util.ArrayList;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
import java.util.Arrays;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.rap.rwt.widgets.DropDown;
|
import org.eclipse.rap.rwt.widgets.DropDown;
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
|
@ -25,10 +27,9 @@ import org.eclipse.swt.widgets.Text;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import java.util.Arrays;
|
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||||
import java.util.List;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public final class MultiSelectionCombo extends Composite implements Selection {
|
public final class MultiSelectionCombo extends Composite implements Selection {
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ public final class MultiSelectionCombo extends Composite implements Selection {
|
||||||
setLayout(gridLayout);
|
setLayout(gridLayout);
|
||||||
|
|
||||||
this.addListener(SWT.Resize, this::adaptColumnWidth);
|
this.addListener(SWT.Resize, this::adaptColumnWidth);
|
||||||
this.textInput = widgetFactory.textInput(this);
|
this.textInput = widgetFactory.textInput(this, "selection");
|
||||||
this.textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
|
this.textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
|
||||||
this.textInput.setLayoutData(this.textCell);
|
this.textInput.setLayoutData(this.textCell);
|
||||||
this.dropDown = new DropDown(this.textInput, SWT.NONE);
|
this.dropDown = new DropDown(this.textInput, SWT.NONE);
|
||||||
|
|
|
@ -152,7 +152,8 @@ public final class ThresholdList extends Composite {
|
||||||
} else {
|
} else {
|
||||||
Double.parseDouble(s);
|
Double.parseDouble(s);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
VALUE_TEXT_KEY);
|
||||||
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||||
valueInput.setLayoutData(valueCell);
|
valueInput.setLayoutData(valueCell);
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,10 @@ public class WidgetFactory {
|
||||||
private static final String ADD_HTML_ATTR_TEST_ID = "test-id";
|
private static final String ADD_HTML_ATTR_TEST_ID = "test-id";
|
||||||
private static final String SUB_TITLE_TExT_SUFFIX = ".subtitle";
|
private static final String SUB_TITLE_TExT_SUFFIX = ".subtitle";
|
||||||
|
|
||||||
|
public enum AriaRole {
|
||||||
|
link
|
||||||
|
}
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class);
|
private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class);
|
||||||
|
|
||||||
public static final int TEXT_AREA_INPUT_MIN_HEIGHT = 100;
|
public static final int TEXT_AREA_INPUT_MIN_HEIGHT = 100;
|
||||||
|
@ -363,18 +367,21 @@ public class WidgetFactory {
|
||||||
|
|
||||||
public Button buttonLocalized(final Composite parent, final String locTextKey) {
|
public Button buttonLocalized(final Composite parent, final String locTextKey) {
|
||||||
final Button button = new Button(parent, SWT.NONE);
|
final Button button = new Button(parent, SWT.NONE);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Button buttonLocalized(final Composite parent, final LocTextKey locTextKey) {
|
public Button buttonLocalized(final Composite parent, final LocTextKey locTextKey) {
|
||||||
final Button button = new Button(parent, SWT.NONE);
|
final Button button = new Button(parent, SWT.NONE);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, locTextKey);
|
this.polyglotPageService.injectI18n(button, locTextKey);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Button buttonLocalized(final Composite parent, final CustomVariant variant, final String locTextKey) {
|
public Button buttonLocalized(final Composite parent, final CustomVariant variant, final String locTextKey) {
|
||||||
final Button button = new Button(parent, SWT.NONE);
|
final Button button = new Button(parent, SWT.NONE);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
||||||
button.setData(RWT.CUSTOM_VARIANT, variant.key);
|
button.setData(RWT.CUSTOM_VARIANT, variant.key);
|
||||||
return button;
|
return button;
|
||||||
|
@ -387,6 +394,7 @@ public class WidgetFactory {
|
||||||
final LocTextKey toolTipKey) {
|
final LocTextKey toolTipKey) {
|
||||||
|
|
||||||
final Button button = new Button(parent, type);
|
final Button button = new Button(parent, type);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, locTextKey, toolTipKey);
|
this.polyglotPageService.injectI18n(button, locTextKey, toolTipKey);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
@ -453,42 +461,85 @@ public class WidgetFactory {
|
||||||
return labelLocalized;
|
return labelLocalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text textInput(final Composite content) {
|
public Text textInput(final Composite content, final LocTextKey label) {
|
||||||
return textInput(content, false, false);
|
return textInput(content, false, false, this.i18nSupport.getText(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text textLabel(final Composite content) {
|
public Text textInput(final Composite content, final String label) {
|
||||||
return textInput(content, false, true);
|
return textInput(content, false, false, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text passwordInput(final Composite content) {
|
public Text passwordInput(final Composite content, final LocTextKey label) {
|
||||||
return textInput(content, true, false);
|
return textInput(content, true, false, this.i18nSupport.getText(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text textAreaInput(final Composite content, final boolean readonly) {
|
public Text passwordInput(final Composite content, final String label) {
|
||||||
return readonly
|
return textInput(content, true, false, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text textAreaInput(
|
||||||
|
final Composite content,
|
||||||
|
final boolean readonly,
|
||||||
|
final LocTextKey label) {
|
||||||
|
|
||||||
|
return textAreaInput(content, readonly, this.i18nSupport.getText(label));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text textAreaInput(
|
||||||
|
final Composite content,
|
||||||
|
final boolean readonly,
|
||||||
|
final String label) {
|
||||||
|
|
||||||
|
final Text input = readonly
|
||||||
? new Text(content, SWT.LEFT | SWT.MULTI)
|
? new Text(content, SWT.LEFT | SWT.MULTI)
|
||||||
: new Text(content, SWT.LEFT | SWT.BORDER | SWT.MULTI);
|
: new Text(content, SWT.LEFT | SWT.BORDER | SWT.MULTI);
|
||||||
|
if (label != null) {
|
||||||
|
WidgetFactory.setAttribute(input, "aria-label", label);
|
||||||
|
}
|
||||||
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text textInput(final Composite content, final boolean password, final boolean readonly) {
|
public Text textInput(
|
||||||
return readonly
|
final Composite content,
|
||||||
|
final boolean password,
|
||||||
|
final boolean readonly,
|
||||||
|
final LocTextKey label) {
|
||||||
|
|
||||||
|
return textInput(content, password, readonly, this.i18nSupport.getText(label));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text textInput(
|
||||||
|
final Composite content,
|
||||||
|
final boolean password,
|
||||||
|
final boolean readonly,
|
||||||
|
final String label) {
|
||||||
|
|
||||||
|
final Text input = readonly
|
||||||
? new Text(content, SWT.LEFT)
|
? new Text(content, SWT.LEFT)
|
||||||
: new Text(content, (password)
|
: new Text(content, (password)
|
||||||
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
|
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
|
||||||
: SWT.LEFT | SWT.BORDER);
|
: SWT.LEFT | SWT.BORDER);
|
||||||
}
|
|
||||||
|
|
||||||
public Text numberInput(final Composite content, final Consumer<String> numberCheck) {
|
if (label != null) {
|
||||||
return numberInput(content, numberCheck, false);
|
WidgetFactory.setAttribute(input, "aria-label", label);
|
||||||
}
|
|
||||||
|
|
||||||
public Text numberInput(final Composite content, final Consumer<String> numberCheck, final boolean readonly) {
|
|
||||||
if (readonly) {
|
|
||||||
return new Text(content, SWT.LEFT | SWT.READ_ONLY);
|
|
||||||
}
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
final Text numberInput = new Text(content, SWT.RIGHT | SWT.BORDER);
|
public Text numberInput(final Composite content, final Consumer<String> numberCheck, final LocTextKey label) {
|
||||||
|
return numberInput(content, numberCheck, false, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text numberInput(
|
||||||
|
final Composite content,
|
||||||
|
final Consumer<String> numberCheck,
|
||||||
|
final boolean readonly,
|
||||||
|
final LocTextKey label) {
|
||||||
|
|
||||||
|
final Text numberInput = new Text(content, (readonly) ? SWT.LEFT | SWT.READ_ONLY : SWT.RIGHT | SWT.BORDER);
|
||||||
|
if (label != null) {
|
||||||
|
WidgetFactory.setAttribute(numberInput, "aria-label", this.i18nSupport.getText(label));
|
||||||
|
}
|
||||||
if (numberCheck != null) {
|
if (numberCheck != null) {
|
||||||
numberInput.addListener(SWT.Verify, event -> {
|
numberInput.addListener(SWT.Verify, event -> {
|
||||||
final String value = event.text;
|
final String value = event.text;
|
||||||
|
@ -884,11 +935,11 @@ public class WidgetFactory {
|
||||||
setAttribute(widget, ADD_HTML_ATTR_TEST_ID, value);
|
setAttribute(widget, ADD_HTML_ATTR_TEST_ID, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setARIARole(final Widget widget, final String value) {
|
public static void setARIARole(final Widget widget, final AriaRole role) {
|
||||||
setAttribute(widget, ADD_HTML_ATTR_ARIA_ROLE, value);
|
setAttribute(widget, ADD_HTML_ATTR_ARIA_ROLE, role.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setAttribute(final Widget widget, final String name, final String value) {
|
public static void setAttribute(final Widget widget, final String name, final String value) {
|
||||||
if (!widget.isDisposed()) {
|
if (!widget.isDisposed()) {
|
||||||
final String $el = widget instanceof Text ? "$input" : "$el";
|
final String $el = widget instanceof Text ? "$input" : "$el";
|
||||||
final String id = WidgetUtil.getId(widget);
|
final String id = WidgetUtil.getId(widget);
|
||||||
|
|
|
@ -60,7 +60,5 @@ public class CacheConfig extends JCacheConfigurerSupport {
|
||||||
log.error("Failed to initialize caching with EHCache. Fallback to simple caching");
|
log.error("Failed to initialize caching with EHCache. Fallback to simple caching");
|
||||||
return new ConcurrentMapCacheManager();
|
return new ConcurrentMapCacheManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice;
|
||||||
|
|
||||||
|
import org.flywaydb.core.Flyway;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@WebServiceProfile
|
||||||
|
public class SEBServerMigrationStrategy implements FlywayMigrationStrategy {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SEBServerMigrationStrategy.class);
|
||||||
|
|
||||||
|
private final boolean cleanDBOnStartup;
|
||||||
|
private final WebserviceInfo webserviceInfo;
|
||||||
|
private final WebserviceInfoDAO webserviceInfoDAO;
|
||||||
|
|
||||||
|
public SEBServerMigrationStrategy(
|
||||||
|
final WebserviceInfo webserviceInfo,
|
||||||
|
final WebserviceInfoDAO webserviceInfoDAO,
|
||||||
|
@Value("${sebserver.webservice.clean-db-on-startup:false}") final boolean cleanDBOnStartup) {
|
||||||
|
|
||||||
|
this.webserviceInfo = webserviceInfo;
|
||||||
|
this.webserviceInfoDAO = webserviceInfoDAO;
|
||||||
|
this.cleanDBOnStartup = cleanDBOnStartup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(final Flyway flyway) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// If we are in a distributed setup only apply migration task if this is the master service
|
||||||
|
// or if there was no data base initialization yet at all.
|
||||||
|
if (this.webserviceInfo.isDistributed()) {
|
||||||
|
if (this.webserviceInfoDAO.isInitialized()) {
|
||||||
|
final boolean isMaster = this.webserviceInfoDAO.isMaster(this.webserviceInfo.getWebserviceUUID());
|
||||||
|
if (!isMaster) {
|
||||||
|
log.info(
|
||||||
|
"Skip migration task since this is not a master instance: {}",
|
||||||
|
this.webserviceInfo.getWebserviceUUID());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cleanDBOnStartup) {
|
||||||
|
flyway.clean();
|
||||||
|
}
|
||||||
|
flyway.migrate();
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to apply migration task: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,13 +10,9 @@ package ch.ethz.seb.sebserver.webservice;
|
||||||
|
|
||||||
import org.cryptonode.jncryptor.AES256JNCryptor;
|
import org.cryptonode.jncryptor.AES256JNCryptor;
|
||||||
import org.cryptonode.jncryptor.JNCryptor;
|
import org.cryptonode.jncryptor.JNCryptor;
|
||||||
import org.flywaydb.core.Flyway;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
@ -25,9 +21,6 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
public class WebserviceConfig {
|
public class WebserviceConfig {
|
||||||
|
|
||||||
@Value("${sebserver.webservice.clean-db-on-startup:false}")
|
|
||||||
boolean cleanDBOnStartup;
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Bean
|
@Bean
|
||||||
public JNCryptor jnCryptor() {
|
public JNCryptor jnCryptor() {
|
||||||
|
@ -36,24 +29,4 @@ public class WebserviceConfig {
|
||||||
return aes256jnCryptor;
|
return aes256jnCryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** For test, development and demo profile, we want to always clean up and
|
|
||||||
* Start the migration from scratch to work with the same data.
|
|
||||||
*
|
|
||||||
* @return FlywayMigrationStrategy for "dev-ws", "test", "demo" profiles */
|
|
||||||
@Bean
|
|
||||||
@Profile(value = { "dev-ws", "test", "demo" })
|
|
||||||
public FlywayMigrationStrategy cleanMigrateStrategy() {
|
|
||||||
final FlywayMigrationStrategy strategy = new FlywayMigrationStrategy() {
|
|
||||||
@Override
|
|
||||||
public void migrate(final Flyway flyway) {
|
|
||||||
if (WebserviceConfig.this.cleanDBOnStartup) {
|
|
||||||
flyway.clean();
|
|
||||||
}
|
|
||||||
flyway.migrate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,10 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
|
||||||
SEBServerInit.INIT_LOGGER.info("---->");
|
SEBServerInit.INIT_LOGGER.info("---->");
|
||||||
SEBServerInit.INIT_LOGGER.info("----> *** Info:");
|
SEBServerInit.INIT_LOGGER.info("----> *** Info:");
|
||||||
|
|
||||||
|
if (this.webserviceInfo.isDistributed()) {
|
||||||
|
SEBServerInit.INIT_LOGGER.info("----> Distributed Setup: {}", this.webserviceInfo.getWebserviceUUID());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address"));
|
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address"));
|
||||||
SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port"));
|
SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port"));
|
||||||
|
@ -126,6 +130,7 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
|
||||||
|
|
||||||
SEBServerInit.INIT_LOGGER.info("---->");
|
SEBServerInit.INIT_LOGGER.info("---->");
|
||||||
SEBServerInit.INIT_LOGGER.info("----> Unregister Webservice: {}", this.webserviceInfo.getWebserviceUUID());
|
SEBServerInit.INIT_LOGGER.info("----> Unregister Webservice: {}", this.webserviceInfo.getWebserviceUUID());
|
||||||
|
|
||||||
this.webserviceInfoDAO.unregister(this.webserviceInfo.getWebserviceUUID());
|
this.webserviceInfoDAO.unregister(this.webserviceInfo.getWebserviceUUID());
|
||||||
|
|
||||||
SEBServerInit.INIT_LOGGER.info("---->");
|
SEBServerInit.INIT_LOGGER.info("---->");
|
||||||
|
|
|
@ -69,7 +69,8 @@ public interface AdditionalAttributesDAO {
|
||||||
|
|
||||||
/** Use this to delete all additional attributes for a given entity.
|
/** Use this to delete all additional attributes for a given entity.
|
||||||
*
|
*
|
||||||
|
* @param type the entity type
|
||||||
* @param entityId the entity identifier (primary-key) */
|
* @param entityId the entity identifier (primary-key) */
|
||||||
void deleteAll(Long entityId);
|
void deleteAll(EntityType type, Long entityId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,9 +121,17 @@ public interface RemoteProctoringRoomDAO {
|
||||||
|
|
||||||
/** Get currently active break-out rooms for given connectionToken
|
/** Get currently active break-out rooms for given connectionToken
|
||||||
*
|
*
|
||||||
* @param examId The exam identifier of the connection
|
|
||||||
* @param connectionTokens The connection token of the client connection
|
* @param connectionTokens The connection token of the client connection
|
||||||
* @return Result refer to active break-out rooms or to an error when happened */
|
* @return Result refer to active break-out rooms or to an error when happened */
|
||||||
Result<Collection<RemoteProctoringRoom>> getBreakoutRooms(String connectionToken);
|
Result<Collection<RemoteProctoringRoom>> getBreakoutRooms(String connectionToken);
|
||||||
|
|
||||||
|
/** Get a list of client connection tokens of connections that currently are in
|
||||||
|
* break-out rooms, including the town-hall room
|
||||||
|
*
|
||||||
|
* @param examId The exam identifier of the connection
|
||||||
|
* @return Result refer to active break-out rooms or to an error when happened */
|
||||||
|
Result<Collection<String>> getConnectionsInBreakoutRooms(Long examId);
|
||||||
|
|
||||||
|
void setCollectingRoomOpenFlag(Long roomId, boolean isOpen);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||||
* the persistent data table. */
|
* the persistent data table. */
|
||||||
public interface WebserviceInfoDAO {
|
public interface WebserviceInfoDAO {
|
||||||
|
|
||||||
|
boolean isInitialized();
|
||||||
|
|
||||||
/** Register a SEB webservice within the persistent storage
|
/** Register a SEB webservice within the persistent storage
|
||||||
*
|
*
|
||||||
* @param uuid A unique identifier that was generated by the webservice on startup
|
* @param uuid A unique identifier that was generated by the webservice on startup
|
||||||
|
|
|
@ -12,6 +12,8 @@ import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.mybatis.dynamic.sql.SqlBuilder;
|
import org.mybatis.dynamic.sql.SqlBuilder;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
@ -29,6 +31,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO {
|
public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AdditionalAttributesDAOImpl.class);
|
||||||
|
|
||||||
private final AdditionalAttributeRecordMapper additionalAttributeRecordMapper;
|
private final AdditionalAttributeRecordMapper additionalAttributeRecordMapper;
|
||||||
|
|
||||||
protected AdditionalAttributesDAOImpl(final AdditionalAttributeRecordMapper additionalAttributeRecordMapper) {
|
protected AdditionalAttributesDAOImpl(final AdditionalAttributeRecordMapper additionalAttributeRecordMapper) {
|
||||||
|
@ -164,14 +168,21 @@ public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteAll(final Long entityId) {
|
public void deleteAll(final EntityType type, final Long entityId) {
|
||||||
this.additionalAttributeRecordMapper
|
try {
|
||||||
.deleteByExample()
|
this.additionalAttributeRecordMapper
|
||||||
.where(
|
.deleteByExample()
|
||||||
AdditionalAttributeRecordDynamicSqlSupport.entityId,
|
.where(
|
||||||
SqlBuilder.isEqualTo(entityId))
|
AdditionalAttributeRecordDynamicSqlSupport.entityType,
|
||||||
.build()
|
SqlBuilder.isEqualTo(type.name()))
|
||||||
.execute();
|
.and(
|
||||||
|
AdditionalAttributeRecordDynamicSqlSupport.entityId,
|
||||||
|
SqlBuilder.isEqualTo(entityId))
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.warn("Failed to delete all additional attributes for: {} cause: {}", entityId, e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,23 +216,21 @@ public class CertificateDAOImpl implements CertificateDAO {
|
||||||
final boolean[] keyUsage = cert.getKeyUsage();
|
final boolean[] keyUsage = cert.getKeyUsage();
|
||||||
final EnumSet<CertificateType> result = EnumSet.noneOf(CertificateType.class);
|
final EnumSet<CertificateType> result = EnumSet.noneOf(CertificateType.class);
|
||||||
|
|
||||||
// digitalSignature
|
if (keyUsage != null) {
|
||||||
if (keyUsage[0]) {
|
// digitalSignature
|
||||||
result.add(CertificateType.DIGITAL_SIGNATURE);
|
if (keyUsage[0]) {
|
||||||
}
|
result.add(CertificateType.DIGITAL_SIGNATURE);
|
||||||
|
}
|
||||||
|
|
||||||
// dataEncipherment
|
// dataEncipherment
|
||||||
if (keyUsage[2] || keyUsage[3]) {
|
if (keyUsage[2] || keyUsage[3]) {
|
||||||
result.add(CertificateType.DATA_ENCIPHERMENT);
|
result.add(CertificateType.DATA_ENCIPHERMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyCertSign
|
// keyCertSign
|
||||||
if (keyUsage[5]) {
|
if (keyUsage[5]) {
|
||||||
result.add(CertificateType.KEY_CERT_SIGN);
|
result.add(CertificateType.KEY_CERT_SIGN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
result.add(CertificateType.UNKNOWN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final String alias = certificates.keyStore.engineGetCertificateAlias(cert);
|
final String alias = certificates.keyStore.engineGetCertificateAlias(cert);
|
||||||
|
@ -240,6 +238,10 @@ public class CertificateDAOImpl implements CertificateDAO {
|
||||||
result.add(CertificateType.DATA_ENCIPHERMENT_PRIVATE_KEY);
|
result.add(CertificateType.DATA_ENCIPHERMENT_PRIVATE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
result.add(CertificateType.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -635,7 +635,8 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// delete all additional attributes
|
// delete all additional attributes
|
||||||
this.additionalAttributeRecordMapper.deleteByExample()
|
this.additionalAttributeRecordMapper
|
||||||
|
.deleteByExample()
|
||||||
.where(AdditionalAttributeRecordDynamicSqlSupport.entityType, isEqualTo(EntityType.EXAM.name()))
|
.where(AdditionalAttributeRecordDynamicSqlSupport.entityType, isEqualTo(EntityType.EXAM.name()))
|
||||||
.and(AdditionalAttributeRecordDynamicSqlSupport.entityId, isIn(ids))
|
.and(AdditionalAttributeRecordDynamicSqlSupport.entityId, isIn(ids))
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -35,6 +35,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordMapper;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordMapper;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.RemoteProctoringRoomRecord;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.RemoteProctoringRoomRecord;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.NewRoom;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.NewRoom;
|
||||||
|
@ -49,11 +50,14 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
private static final Object RESERVE_ROOM_LOCK = new Object();
|
private static final Object RESERVE_ROOM_LOCK = new Object();
|
||||||
|
|
||||||
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
|
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
|
||||||
|
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||||
|
|
||||||
protected RemoteProctoringRoomDAOImpl(
|
protected RemoteProctoringRoomDAOImpl(
|
||||||
final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper) {
|
final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper,
|
||||||
|
final AdditionalAttributesDAO additionalAttributesDAO) {
|
||||||
|
|
||||||
this.remoteProctoringRoomRecordMapper = remoteProctoringRoomRecordMapper;
|
this.remoteProctoringRoomRecordMapper = remoteProctoringRoomRecordMapper;
|
||||||
|
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -227,6 +231,10 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<EntityKey> deleteRoom(final Long roomId) {
|
public Result<EntityKey> deleteRoom(final Long roomId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
this.additionalAttributesDAO
|
||||||
|
.deleteAll(EntityType.REMOTE_PROCTORING_ROOM, roomId);
|
||||||
|
|
||||||
this.remoteProctoringRoomRecordMapper
|
this.remoteProctoringRoomRecordMapper
|
||||||
.deleteByPrimaryKey(roomId);
|
.deleteByPrimaryKey(roomId);
|
||||||
|
|
||||||
|
@ -249,7 +257,15 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return ids.stream()
|
return ids.stream()
|
||||||
.map(id -> new EntityKey(String.valueOf(id), EntityType.REMOTE_PROCTORING_ROOM))
|
.map(roomId -> {
|
||||||
|
this.additionalAttributesDAO.deleteAll(
|
||||||
|
EntityType.REMOTE_PROCTORING_ROOM,
|
||||||
|
roomId);
|
||||||
|
return roomId;
|
||||||
|
})
|
||||||
|
.map(roomId -> new EntityKey(
|
||||||
|
String.valueOf(roomId),
|
||||||
|
EntityType.REMOTE_PROCTORING_ROOM))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -322,6 +338,37 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<String>> getConnectionsInBreakoutRooms(final Long examId) {
|
||||||
|
return Result.tryCatch(() -> this.remoteProctoringRoomRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
||||||
|
.and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNotNull())
|
||||||
|
.build()
|
||||||
|
.execute()
|
||||||
|
.stream()
|
||||||
|
.flatMap(room -> Arrays.asList(
|
||||||
|
StringUtils.split(
|
||||||
|
room.getBreakOutConnections(),
|
||||||
|
Constants.LIST_SEPARATOR_CHAR))
|
||||||
|
.stream())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void setCollectingRoomOpenFlag(final Long roomId, final boolean isOpen) {
|
||||||
|
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||||
|
EntityType.REMOTE_PROCTORING_ROOM,
|
||||||
|
roomId,
|
||||||
|
RemoteProctoringRoom.ATTR_IS_OPEN,
|
||||||
|
BooleanUtils.toStringTrueFalse(isOpen))
|
||||||
|
.onError(error -> log.error("Failed to set open flag for proctoring room: {} : {}",
|
||||||
|
roomId,
|
||||||
|
error.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) {
|
private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) {
|
||||||
final String breakOutConnections = record.getBreakOutConnections();
|
final String breakOutConnections = record.getBreakOutConnections();
|
||||||
final Collection<String> connections = StringUtils.isNotBlank(breakOutConnections)
|
final Collection<String> connections = StringUtils.isNotBlank(breakOutConnections)
|
||||||
|
@ -337,7 +384,22 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||||
BooleanUtils.toBooleanObject(record.getTownhallRoom()),
|
BooleanUtils.toBooleanObject(record.getTownhallRoom()),
|
||||||
connections,
|
connections,
|
||||||
record.getJoinKey(),
|
record.getJoinKey(),
|
||||||
record.getRoomData());
|
record.getRoomData(),
|
||||||
|
isOpen(record));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOpen(final RemoteProctoringRoomRecord record) {
|
||||||
|
if (record.getTownhallRoom() != 0 || !StringUtils.isBlank(record.getBreakOutConnections())) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return BooleanUtils.toBoolean(this.additionalAttributesDAO
|
||||||
|
.getAdditionalAttribute(
|
||||||
|
EntityType.REMOTE_PROCTORING_ROOM,
|
||||||
|
record.getId(),
|
||||||
|
RemoteProctoringRoom.ATTR_IS_OPEN)
|
||||||
|
.map(rec -> rec.getValue())
|
||||||
|
.getOrElse(() -> Constants.FALSE_STRING));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RemoteProctoringRoomRecord createNewCollectingRoom(
|
private RemoteProctoringRoomRecord createNewCollectingRoom(
|
||||||
|
|
|
@ -47,6 +47,20 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO {
|
||||||
this.forceMaster = forceMaster;
|
this.forceMaster = forceMaster;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean isInitialized() {
|
||||||
|
try {
|
||||||
|
this.webserviceServerInfoRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
return true;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public boolean register(final String uuid, final String address) {
|
public boolean register(final String uuid, final String address) {
|
||||||
|
@ -173,7 +187,7 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO {
|
||||||
.execute();
|
.execute();
|
||||||
return true;
|
return true;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to register webservice: uuid: {}", uuid, e);
|
log.warn("Failed to unregister webservice: uuid: {}, cause: ", uuid, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -328,20 +328,22 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLED_FEATURES)) {
|
if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLED_FEATURES)) {
|
||||||
try {
|
try {
|
||||||
final String value = mapping.get(ProctoringServiceSettings.ATTR_ENABLED_FEATURES).getValue();
|
final String value = mapping.get(ProctoringServiceSettings.ATTR_ENABLED_FEATURES).getValue();
|
||||||
return EnumSet.copyOf(Arrays.asList(StringUtils.split(value, Constants.LIST_SEPARATOR))
|
return StringUtils.isNotBlank(value)
|
||||||
.stream()
|
? EnumSet.copyOf(Arrays.asList(StringUtils.split(value, Constants.LIST_SEPARATOR))
|
||||||
.map(str -> {
|
.stream()
|
||||||
try {
|
.map(str -> {
|
||||||
return ProctoringFeature.valueOf(str);
|
try {
|
||||||
} catch (final Exception e) {
|
return ProctoringFeature.valueOf(str);
|
||||||
log.error(
|
} catch (final Exception e) {
|
||||||
"Failed to enabled single features for proctoring settings. Skipping. {}",
|
log.error(
|
||||||
e.getMessage());
|
"Failed to enabled single features for proctoring settings. Skipping. {}",
|
||||||
return null;
|
e.getMessage());
|
||||||
}
|
return null;
|
||||||
})
|
}
|
||||||
.filter(Objects::nonNull)
|
})
|
||||||
.collect(Collectors.toSet()));
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet()))
|
||||||
|
: EnumSet.noneOf(ProctoringFeature.class);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to get enabled features for proctoring settings. Enable all. {}", e.getMessage());
|
log.error("Failed to get enabled features for proctoring settings. Enable all. {}", e.getMessage());
|
||||||
return EnumSet.allOf(ProctoringFeature.class);
|
return EnumSet.allOf(ProctoringFeature.class);
|
||||||
|
|
|
@ -33,13 +33,20 @@ public interface ExamProctoringRoomService {
|
||||||
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
||||||
Result<Collection<ClientConnection>> getRoomConnections(Long roomId);
|
Result<Collection<ClientConnection>> getRoomConnections(Long roomId);
|
||||||
|
|
||||||
/** Get a collection of all ClientConnection that are currently connected to a specified collecting room.
|
/** Get a collection of all ClientConnection that are registered to a specified collecting room.
|
||||||
*
|
*
|
||||||
* @param examId The exam identifier of the room
|
* @param examId The exam identifier of the room
|
||||||
* @param roomName The room name
|
* @param roomName The room name
|
||||||
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
||||||
Result<Collection<ClientConnection>> getCollectingRoomConnections(Long examId, String roomName);
|
Result<Collection<ClientConnection>> getCollectingRoomConnections(Long examId, String roomName);
|
||||||
|
|
||||||
|
/** Get a collection of all ClientConnection that are currently connected to a specified collecting room.
|
||||||
|
*
|
||||||
|
* @param examId The exam identifier of the room
|
||||||
|
* @param roomName The room name
|
||||||
|
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
||||||
|
Result<Collection<ClientConnection>> getActiveCollectingRoomConnections(Long examId, String roomName);
|
||||||
|
|
||||||
/** This is internally used to update client connections that are flagged for updating
|
/** This is internally used to update client connections that are flagged for updating
|
||||||
* the proctoring room assignment.
|
* the proctoring room assignment.
|
||||||
* This attaches or detaches client connections from or to proctoring rooms of an exam in one batch.
|
* This attaches or detaches client connections from or to proctoring rooms of an exam in one batch.
|
||||||
|
|
|
@ -14,14 +14,12 @@ import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.springframework.cache.CacheManager;
|
import org.springframework.cache.CacheManager;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
|
@ -185,19 +183,7 @@ public interface ExamSessionService {
|
||||||
* @param connection ClientConnectionData instance
|
* @param connection ClientConnectionData instance
|
||||||
* @return true if the given ClientConnectionData is an active SEB client connection */
|
* @return true if the given ClientConnectionData is an active SEB client connection */
|
||||||
static boolean isActiveConnection(final ClientConnectionData connection) {
|
static boolean isActiveConnection(final ClientConnectionData connection) {
|
||||||
if (connection.clientConnection.status.establishedStatus) {
|
return connection.clientConnection.status.clientActiveStatus;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connection.clientConnection.status == ConnectionStatus.CONNECTION_REQUESTED) {
|
|
||||||
final Long creationTime = connection.clientConnection.getCreationTime();
|
|
||||||
final long millisecondsNow = Utils.getMillisecondsNow();
|
|
||||||
if (millisecondsNow - creationTime < 30 * Constants.SECOND_IN_MILLIS) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -18,6 +19,7 @@ import java.util.stream.Collectors;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@ -54,19 +56,22 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
private final ExamAdminService examAdminService;
|
private final ExamAdminService examAdminService;
|
||||||
private final ExamSessionService examSessionService;
|
private final ExamSessionService examSessionService;
|
||||||
private final SEBClientInstructionService sebInstructionService;
|
private final SEBClientInstructionService sebInstructionService;
|
||||||
|
private final boolean sendBroadcastReset;
|
||||||
|
|
||||||
public ExamProctoringRoomServiceImpl(
|
public ExamProctoringRoomServiceImpl(
|
||||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||||
final ClientConnectionDAO clientConnectionDAO,
|
final ClientConnectionDAO clientConnectionDAO,
|
||||||
final ExamAdminService examAdminService,
|
final ExamAdminService examAdminService,
|
||||||
final ExamSessionService examSessionService,
|
final ExamSessionService examSessionService,
|
||||||
final SEBClientInstructionService sebInstructionService) {
|
final SEBClientInstructionService sebInstructionService,
|
||||||
|
@Value("${sebserver.webservice.proctoring.resetBroadcastOnLeav:true}") final boolean sendBroadcastReset) {
|
||||||
|
|
||||||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||||
this.clientConnectionDAO = clientConnectionDAO;
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
this.examAdminService = examAdminService;
|
this.examAdminService = examAdminService;
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionService = examSessionService;
|
||||||
this.sebInstructionService = sebInstructionService;
|
this.sebInstructionService = sebInstructionService;
|
||||||
|
this.sendBroadcastReset = sendBroadcastReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -80,8 +85,34 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Collection<ClientConnection>> getCollectingRoomConnections(final Long examId, final String roomName) {
|
public Result<Collection<ClientConnection>> getCollectingRoomConnections(
|
||||||
return this.clientConnectionDAO.getCollectingRoomConnections(examId, roomName);
|
final Long examId,
|
||||||
|
final String roomName) {
|
||||||
|
|
||||||
|
return this.clientConnectionDAO
|
||||||
|
.getCollectingRoomConnections(examId, roomName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Collection<ClientConnection>> getActiveCollectingRoomConnections(
|
||||||
|
final Long examId,
|
||||||
|
final String roomName) {
|
||||||
|
|
||||||
|
final Collection<String> currentlyInBreakoutRooms = this.remoteProctoringRoomDAO
|
||||||
|
.getConnectionsInBreakoutRooms(examId)
|
||||||
|
.getOrElse(() -> Collections.emptyList());
|
||||||
|
|
||||||
|
if (currentlyInBreakoutRooms.isEmpty()) {
|
||||||
|
return this.clientConnectionDAO
|
||||||
|
.getCollectingRoomConnections(examId, roomName);
|
||||||
|
} else {
|
||||||
|
return this.clientConnectionDAO
|
||||||
|
.getCollectingRoomConnections(examId, roomName)
|
||||||
|
.map(connections -> connections
|
||||||
|
.stream()
|
||||||
|
.filter(cc -> !currentlyInBreakoutRooms.contains(cc.connectionToken))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -230,7 +261,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
} else if (remoteProctoringRoom.townhallRoom) {
|
} else if (remoteProctoringRoom.townhallRoom) {
|
||||||
closeTownhall(examId, settings, examProctoringService);
|
closeTownhall(examId, settings, examProctoringService);
|
||||||
} else {
|
} else {
|
||||||
closeCollectingRoom(examId, roomName, examProctoringService);
|
closeCollectingRoom(examId, roomName, settings, examProctoringService);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -356,13 +387,16 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
.notifyBreakOutRoomOpened(proctoringSettings, room)
|
.notifyBreakOutRoomOpened(proctoringSettings, room)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
} else {
|
} else {
|
||||||
final Collection<ClientConnection> clientConnections = this.clientConnectionDAO
|
final Collection<ClientConnection> clientConnections = this
|
||||||
.getCollectingRoomConnections(examId, roomName)
|
.getActiveCollectingRoomConnections(examId, roomName)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
examProctoringService
|
examProctoringService
|
||||||
.notifyCollectingRoomOpened(proctoringSettings, room, clientConnections)
|
.notifyCollectingRoomOpened(proctoringSettings, room, clientConnections)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
this.remoteProctoringRoomDAO
|
||||||
|
.setCollectingRoomOpenFlag(room.id, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -377,11 +411,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
.getActiveConnectionTokens(examId)
|
.getActiveConnectionTokens(examId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// Send default settings to clients
|
// Send default settings to clients if fearture is enabled
|
||||||
this.sendReconfigurationInstructions(
|
if (this.sendBroadcastReset) {
|
||||||
examId,
|
this.sendReconfigurationInstructions(
|
||||||
connectionTokens,
|
examId,
|
||||||
examProctoringService.getDefaultReconfigInstructionAttributes());
|
connectionTokens,
|
||||||
|
examProctoringService.getDefaultReconfigInstructionAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
// Close and delete town-hall room
|
// Close and delete town-hall room
|
||||||
this.remoteProctoringRoomDAO
|
this.remoteProctoringRoomDAO
|
||||||
|
@ -403,20 +439,36 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
private void closeCollectingRoom(
|
private void closeCollectingRoom(
|
||||||
final Long examId,
|
final Long examId,
|
||||||
final String roomName,
|
final String roomName,
|
||||||
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
final ExamProctoringService examProctoringService) {
|
final ExamProctoringService examProctoringService) {
|
||||||
|
|
||||||
// get all connections of the room
|
// get all connections of the room
|
||||||
final List<String> connectionTokens = this.getCollectingRoomConnections(examId, roomName)
|
final List<String> connectionTokens = this.getActiveCollectingRoomConnections(examId, roomName)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.stream()
|
.stream()
|
||||||
.map(cc -> cc.connectionToken)
|
.map(cc -> cc.connectionToken)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// Send default settings to clients
|
final RemoteProctoringRoom room = this.remoteProctoringRoomDAO
|
||||||
this.sendReconfigurationInstructions(
|
.getRoom(examId, roomName)
|
||||||
examId,
|
.onError(error -> log.error("Failed to get room for setting closed: {} {} {}",
|
||||||
connectionTokens,
|
examId,
|
||||||
examProctoringService.getDefaultReconfigInstructionAttributes());
|
roomName,
|
||||||
|
error.getMessage()))
|
||||||
|
.getOr(null);
|
||||||
|
|
||||||
|
if (room != null) {
|
||||||
|
this.remoteProctoringRoomDAO
|
||||||
|
.setCollectingRoomOpenFlag(room.id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send default settings to clients if feature is enabled
|
||||||
|
if (this.sendBroadcastReset) {
|
||||||
|
this.sendReconfigurationInstructions(
|
||||||
|
examId,
|
||||||
|
connectionTokens,
|
||||||
|
examProctoringService.getDefaultReconfigInstructionAttributes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupBreakOutRooms(final ClientConnectionRecord cc) {
|
private void cleanupBreakOutRooms(final ClientConnectionRecord cc) {
|
||||||
|
@ -453,11 +505,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
final ExamProctoringService examProctoringService,
|
final ExamProctoringService examProctoringService,
|
||||||
final RemoteProctoringRoom remoteProctoringRoom) {
|
final RemoteProctoringRoom remoteProctoringRoom) {
|
||||||
|
|
||||||
// Send default settings to clients
|
// Send default settings to clients if feature is enabled
|
||||||
this.sendReconfigurationInstructions(
|
if (this.sendBroadcastReset) {
|
||||||
examId,
|
this.sendReconfigurationInstructions(
|
||||||
remoteProctoringRoom.breakOutConnections,
|
examId,
|
||||||
examProctoringService.getDefaultReconfigInstructionAttributes());
|
remoteProctoringRoom.breakOutConnections,
|
||||||
|
examProctoringService.getDefaultReconfigInstructionAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
// Dispose the proctoring room on service side
|
// Dispose the proctoring room on service side
|
||||||
examProctoringService
|
examProctoringService
|
||||||
|
@ -509,7 +563,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
} else if (!room.breakOutConnections.isEmpty()) {
|
} else if (!room.breakOutConnections.isEmpty()) {
|
||||||
connectionTokens.addAll(room.breakOutConnections);
|
connectionTokens.addAll(room.breakOutConnections);
|
||||||
} else {
|
} else {
|
||||||
connectionTokens.addAll(this.getCollectingRoomConnections(examId, roomName)
|
connectionTokens.addAll(this.getActiveCollectingRoomConnections(examId, roomName)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.stream()
|
.stream()
|
||||||
.map(cc -> cc.connectionToken)
|
.map(cc -> cc.connectionToken)
|
||||||
|
@ -630,16 +684,17 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
final ClientConnectionData clientConnection = this.examSessionService
|
final ClientConnectionData clientConnection = this.examSessionService
|
||||||
.getConnectionData(connectionToken)
|
.getConnectionData(connectionToken)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
final String roomName = this.remoteProctoringRoomDAO
|
|
||||||
.getRoomName(clientConnection.clientConnection.getRemoteProctoringRoomId())
|
final RemoteProctoringRoom remoteProctoringRoom = this.remoteProctoringRoomDAO
|
||||||
|
.getRoom(clientConnection.clientConnection.getRemoteProctoringRoomId())
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
final ProctoringRoomConnection proctoringConnection = examProctoringService
|
final ProctoringRoomConnection proctoringConnection = examProctoringService
|
||||||
.getClientRoomConnection(
|
.getClientRoomConnection(
|
||||||
proctoringSettings,
|
proctoringSettings,
|
||||||
clientConnection.clientConnection.connectionToken,
|
clientConnection.clientConnection.connectionToken,
|
||||||
roomName,
|
remoteProctoringRoom.name,
|
||||||
clientConnection.clientConnection.userSessionId)
|
remoteProctoringRoom.subject)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
sendJoinInstruction(
|
sendJoinInstruction(
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.joda.time.DateTimeZone;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
@ -125,6 +126,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||||
private final AuthorizationService authorizationService;
|
private final AuthorizationService authorizationService;
|
||||||
private final SEBClientInstructionService sebInstructionService;
|
private final SEBClientInstructionService sebInstructionService;
|
||||||
|
private final boolean enableWaitingRoom;
|
||||||
|
private final boolean sendRejoinForCollectingRoom;
|
||||||
|
|
||||||
public ZoomProctoringService(
|
public ZoomProctoringService(
|
||||||
final ExamSessionService examSessionService,
|
final ExamSessionService examSessionService,
|
||||||
|
@ -134,7 +137,9 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||||
final AuthorizationService authorizationService,
|
final AuthorizationService authorizationService,
|
||||||
final SEBClientInstructionService sebInstructionService) {
|
final SEBClientInstructionService sebInstructionService,
|
||||||
|
@Value("${sebserver.webservice.proctoring.enableWaitingRoom:false}") final boolean enableWaitingRoom,
|
||||||
|
@Value("${sebserver.webservice.proctoring.sendRejoinForCollectingRoom:true}") final boolean sendRejoinForCollectingRoom) {
|
||||||
|
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionService = examSessionService;
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
|
@ -145,6 +150,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
this.sebInstructionService = sebInstructionService;
|
this.sebInstructionService = sebInstructionService;
|
||||||
|
this.enableWaitingRoom = enableWaitingRoom;
|
||||||
|
this.sendRejoinForCollectingRoom = sendRejoinForCollectingRoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -443,23 +450,33 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
if (!this.sendRejoinForCollectingRoom) {
|
||||||
|
// does nothing if the rejoin feature is not enabled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.remoteProctoringRoomDAO.isTownhallRoomActive(proctoringSettings.examId)) {
|
if (this.remoteProctoringRoomDAO.isTownhallRoomActive(proctoringSettings.examId)) {
|
||||||
// do nothing is the town-hall of this exam is open. The clients will automatically join
|
// does nothing if the town-hall of this exam is open. The clients will automatically join
|
||||||
// the meeting once the town-hall has been closed
|
// the meeting once the town-hall has been closed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ProctoringRoomConnection proctoringRoomConnection = this.getProctorRoomConnection(
|
|
||||||
proctoringSettings,
|
|
||||||
room.name,
|
|
||||||
room.subject)
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
clientConnections.stream()
|
clientConnections.stream()
|
||||||
.forEach(cc -> sendJoinInstruction(
|
.forEach(cc -> {
|
||||||
proctoringSettings.examId,
|
try {
|
||||||
cc.connectionToken,
|
sendJoinInstruction(
|
||||||
proctoringRoomConnection));
|
proctoringSettings.examId,
|
||||||
|
cc.connectionToken,
|
||||||
|
getClientRoomConnection(
|
||||||
|
proctoringSettings,
|
||||||
|
cc.connectionToken,
|
||||||
|
room.name,
|
||||||
|
room.subject)
|
||||||
|
.getOrThrow());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to send rejoin instruction to SEB client: {}", cc.connectionToken, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,6 +514,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
proctoringSettings.serverURL,
|
proctoringSettings.serverURL,
|
||||||
credentials,
|
credentials,
|
||||||
roomName);
|
roomName);
|
||||||
|
|
||||||
final UserResponse userResponse = this.jsonMapper.readValue(
|
final UserResponse userResponse = this.jsonMapper.readValue(
|
||||||
createUser.getBody(),
|
createUser.getBody(),
|
||||||
UserResponse.class);
|
UserResponse.class);
|
||||||
|
@ -509,7 +527,9 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
userResponse.id,
|
userResponse.id,
|
||||||
subject,
|
subject,
|
||||||
duration,
|
duration,
|
||||||
meetingPwd);
|
meetingPwd,
|
||||||
|
this.enableWaitingRoom);
|
||||||
|
|
||||||
final MeetingResponse meetingResponse = this.jsonMapper.readValue(
|
final MeetingResponse meetingResponse = this.jsonMapper.readValue(
|
||||||
createMeeting.getBody(),
|
createMeeting.getBody(),
|
||||||
MeetingResponse.class);
|
MeetingResponse.class);
|
||||||
|
@ -520,6 +540,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
userResponse.id,
|
userResponse.id,
|
||||||
meetingResponse.start_url,
|
meetingResponse.start_url,
|
||||||
meetingResponse.join_url);
|
meetingResponse.join_url);
|
||||||
|
|
||||||
final String additionalZoomRoomDataString = this.jsonMapper
|
final String additionalZoomRoomDataString = this.jsonMapper
|
||||||
.writeValueAsString(additionalZoomRoomData);
|
.writeValueAsString(additionalZoomRoomData);
|
||||||
|
|
||||||
|
@ -558,23 +579,29 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
||||||
|
|
||||||
final String jwtHeaderPart = urlEncoder.encodeToString(
|
final String jwtHeaderPart = urlEncoder
|
||||||
ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
.encodeToString(ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
final String jwtPayload = String.format(
|
final String jwtPayload = String.format(
|
||||||
ZOOM_API_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
|
ZOOM_API_ACCESS_TOKEN_PAYLOAD
|
||||||
|
.replaceAll(" ", "")
|
||||||
|
.replaceAll("\n", ""),
|
||||||
credentials.clientIdAsString(),
|
credentials.clientIdAsString(),
|
||||||
expTime);
|
expTime);
|
||||||
final String jwtPayloadPart = urlEncoder.encodeToString(
|
|
||||||
jwtPayload.getBytes(StandardCharsets.UTF_8));
|
final String jwtPayloadPart = urlEncoder
|
||||||
|
.encodeToString(jwtPayload.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
final String message = jwtHeaderPart + "." + jwtPayloadPart;
|
final String message = jwtHeaderPart + "." + jwtPayloadPart;
|
||||||
|
|
||||||
final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG);
|
final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG);
|
||||||
final SecretKeySpec secret_key = new SecretKeySpec(
|
final SecretKeySpec secret_key = new SecretKeySpec(
|
||||||
Utils.toByteArray(decryptedSecret),
|
Utils.toByteArray(decryptedSecret),
|
||||||
TOKEN_ENCODE_ALG);
|
TOKEN_ENCODE_ALG);
|
||||||
|
|
||||||
sha256_HMAC.init(secret_key);
|
sha256_HMAC.init(secret_key);
|
||||||
final String hash = urlEncoder.encodeToString(
|
final String hash = urlEncoder
|
||||||
sha256_HMAC.doFinal(Utils.toByteArray(message)));
|
.encodeToString(sha256_HMAC.doFinal(Utils.toByteArray(message)));
|
||||||
|
|
||||||
builder.append(message)
|
builder.append(message)
|
||||||
.append(".")
|
.append(".")
|
||||||
|
@ -705,7 +732,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final String userId,
|
final String userId,
|
||||||
final String topic,
|
final String topic,
|
||||||
final int duration,
|
final int duration,
|
||||||
final CharSequence password) {
|
final CharSequence password,
|
||||||
|
final boolean waitingRoom) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
@ -718,7 +746,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final CreateMeetingRequest createRoomRequest = new CreateMeetingRequest(
|
final CreateMeetingRequest createRoomRequest = new CreateMeetingRequest(
|
||||||
topic,
|
topic,
|
||||||
duration,
|
duration,
|
||||||
password);
|
password,
|
||||||
|
waitingRoom);
|
||||||
|
|
||||||
final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(createRoomRequest);
|
final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(createRoomRequest);
|
||||||
final HttpHeaders headers = getHeaders(credentials);
|
final HttpHeaders headers = getHeaders(credentials);
|
||||||
|
|
|
@ -123,7 +123,8 @@ public interface ZoomRoomRequestResponse {
|
||||||
public CreateMeetingRequest(
|
public CreateMeetingRequest(
|
||||||
final String topic,
|
final String topic,
|
||||||
final int duration,
|
final int duration,
|
||||||
final CharSequence password) {
|
final CharSequence password,
|
||||||
|
final boolean waitingRoom) {
|
||||||
|
|
||||||
this.type = 2; // Scheduled Meeting
|
this.type = 2; // Scheduled Meeting
|
||||||
this.start_time = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss");
|
this.start_time = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss");
|
||||||
|
@ -131,18 +132,24 @@ public interface ZoomRoomRequestResponse {
|
||||||
this.timezone = DateTimeZone.UTC.getID();
|
this.timezone = DateTimeZone.UTC.getID();
|
||||||
this.topic = topic;
|
this.topic = topic;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.settings = new Settings();
|
this.settings = new Settings(waitingRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static class Settings {
|
static class Settings {
|
||||||
@JsonProperty final boolean host_video = true;
|
@JsonProperty final boolean host_video = false;
|
||||||
@JsonProperty final boolean participant_video = true;
|
@JsonProperty final boolean mute_upon_entry = false;
|
||||||
@JsonProperty final boolean join_before_host = true;
|
@JsonProperty final boolean join_before_host;
|
||||||
@JsonProperty final int jbh_time = 0;
|
@JsonProperty final int jbh_time = 0;
|
||||||
@JsonProperty final boolean use_pmi = false;
|
@JsonProperty final boolean use_pmi = false;
|
||||||
@JsonProperty final String audio = "voip";
|
@JsonProperty final String audio = "voip";
|
||||||
@JsonProperty final boolean waiting_room = false;
|
@JsonProperty final boolean waiting_room;
|
||||||
|
@JsonProperty final boolean allow_multiple_devices = false;
|
||||||
|
|
||||||
|
public Settings(final boolean waitingRoom) {
|
||||||
|
this.join_before_host = !waitingRoom;
|
||||||
|
this.waiting_room = waitingRoom;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,10 @@ public class ExamAPI_V1_Controller {
|
||||||
final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER);
|
final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER);
|
||||||
final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM);
|
final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM);
|
||||||
|
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.trace("****************** SEB client connection: {}", connectionToken);
|
||||||
|
}
|
||||||
|
|
||||||
if (instructionConfirm != null) {
|
if (instructionConfirm != null) {
|
||||||
this.sebClientConnectionService.confirmInstructionDone(connectionToken, instructionConfirm);
|
this.sebClientConnectionService.confirmInstructionDone(connectionToken, instructionConfirm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ logging.level.ch=INFO
|
||||||
logging.level.org.springframework.cache=INFO
|
logging.level.org.springframework.cache=INFO
|
||||||
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl=DEBUG
|
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl=DEBUG
|
||||||
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.session=DEBUG
|
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.session=DEBUG
|
||||||
|
logging.level.ch.ethz.seb.sebserver.webservice.weblayer.api.ExamAPI_V1_Controller=TRACE
|
||||||
|
|
||||||
sebserver.http.client.connect-timeout=150000
|
sebserver.http.client.connect-timeout=150000
|
||||||
sebserver.http.client.connection-request-timeout=100000
|
sebserver.http.client.connection-request-timeout=100000
|
||||||
|
|
|
@ -66,3 +66,7 @@ sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
||||||
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
|
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
|
||||||
sebserver.webservice.lms.address.alias=
|
sebserver.webservice.lms.address.alias=
|
||||||
|
|
||||||
|
sebserver.webservice.proctoring.resetBroadcastOnLeav=true
|
||||||
|
sebserver.webservice.proctoring.zoom.enableWaitingRoom=false
|
||||||
|
sebserver.webservice.proctoring.zoom.sendRejoinForCollectingRoom=true
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@ INSERT IGNORE INTO configuration_attribute VALUES
|
||||||
(960, 'allowFind', 'CHECKBOX', null, null, null, null, 'true'),
|
(960, 'allowFind', 'CHECKBOX', null, null, null, null, 'true'),
|
||||||
(961, 'allowPDFReaderToolbar', 'CHECKBOX', null, null, null, null, 'false'),
|
(961, 'allowPDFReaderToolbar', 'CHECKBOX', null, null, null, null, 'false'),
|
||||||
|
|
||||||
(970, 'setVmwareConfiguration', 'CHECKBOX', null, null, null, null, 'false')
|
(970, 'setVmwareConfiguration', 'CHECKBOX', null, null, null, null, 'false'),
|
||||||
|
(971, 'allowedDisplaysIgnoreFailure', 'CHECKBOX', null, null, null, null, 'false')
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
-- -----------------------------------------------------
|
-- -----------------------------------------------------
|
||||||
|
@ -66,6 +68,11 @@ INSERT IGNORE INTO orientation (config_attribute_id, template_id, view_id, group
|
||||||
-- insert Set VMWare Configuration
|
-- insert Set VMWare Configuration
|
||||||
INSERT IGNORE INTO orientation (config_attribute_id, template_id, view_id, group_id, x_position, y_position, width, height, title) VALUES
|
INSERT IGNORE INTO orientation (config_attribute_id, template_id, view_id, group_id, x_position, y_position, width, height, title) VALUES
|
||||||
(970, 0, 10, 'registry', 0, 7, 4, 1, 'NONE');
|
(970, 0, 10, 'registry', 0, 7, 4, 1, 'NONE');
|
||||||
|
|
||||||
|
-- insert mac settings
|
||||||
|
|
||||||
|
INSERT IGNORE INTO orientation (config_attribute_id, template_id, view_id, group_id, x_position, y_position, width, height, title) VALUES
|
||||||
|
(971, 0, 9, 'macSettings', 7, 11, 5, 1, 'NONE');
|
||||||
|
|
||||||
-- -----------------------------------------------------
|
-- -----------------------------------------------------
|
||||||
-- Update old orientations
|
-- Update old orientations
|
||||||
|
@ -105,7 +112,11 @@ UPDATE orientation SET group_id='additionalWindow', x_position=0, y_position=14,
|
||||||
UPDATE orientation SET x_position=7, y_position=11, width=5 WHERE config_attribute_id=57;
|
UPDATE orientation SET x_position=7, y_position=11, width=5 WHERE config_attribute_id=57;
|
||||||
UPDATE orientation SET x_position=7, y_position=13, width=5 WHERE config_attribute_id=58;
|
UPDATE orientation SET x_position=7, y_position=13, width=5 WHERE config_attribute_id=58;
|
||||||
|
|
||||||
-- insert Set VMWare Configuration
|
-- update Set VMWare Configuration
|
||||||
UPDATE orientation SET y_position=8 WHERE config_attribute_id=406;
|
UPDATE orientation SET y_position=8 WHERE config_attribute_id=406;
|
||||||
UPDATE orientation SET y_position=9 WHERE config_attribute_id=407;
|
UPDATE orientation SET y_position=9 WHERE config_attribute_id=407;
|
||||||
UPDATE orientation SET y_position=10 WHERE config_attribute_id=408;
|
UPDATE orientation SET y_position=10 WHERE config_attribute_id=408;
|
||||||
|
|
||||||
|
-- update mac settings
|
||||||
|
UPDATE orientation SET y_position=10 WHERE config_attribute_id=315;
|
||||||
|
UPDATE orientation SET y_position=12 WHERE config_attribute_id=316;
|
||||||
|
|
|
@ -666,6 +666,9 @@ sebserver.exam.proctoring.form.features.TOWN_HALL=Town-Hall Room
|
||||||
sebserver.exam.proctoring.form.features.ONE_TO_ONE=One to One Room
|
sebserver.exam.proctoring.form.features.ONE_TO_ONE=One to One Room
|
||||||
sebserver.exam.proctoring.form.features.BROADCAST=Broadcasting Feature
|
sebserver.exam.proctoring.form.features.BROADCAST=Broadcasting Feature
|
||||||
sebserver.exam.proctoring.form.features.ENABLE_CHAT=Chat Feature
|
sebserver.exam.proctoring.form.features.ENABLE_CHAT=Chat Feature
|
||||||
|
sebserver.exam.proctoring.form.features.WAITING_ROOM=Enable waiting room for collecting rooms
|
||||||
|
sebserver.exam.proctoring.form.features.SEND_REJOIN_COLLECTING_ROOM=Force rejoin for collecting rooms
|
||||||
|
sebserver.exam.proctoring.form.features.RESET_BROADCAST_ON_LAVE=Reset broadcast on leave
|
||||||
|
|
||||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
|
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
|
||||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring
|
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring
|
||||||
|
@ -1296,6 +1299,8 @@ sebserver.examconfig.props.label.logLevel.4=Verbose
|
||||||
sebserver.examconfig.props.label.logLevel.4.tooltip=Verbose level contains events of all levels
|
sebserver.examconfig.props.label.logLevel.4.tooltip=Verbose level contains events of all levels
|
||||||
sebserver.examconfig.props.label.allowApplicationLog=Allow access to application log (Win)
|
sebserver.examconfig.props.label.allowApplicationLog=Allow access to application log (Win)
|
||||||
sebserver.examconfig.props.label.showApplicationLogButton=Show log button on taskbar (Win)
|
sebserver.examconfig.props.label.showApplicationLogButton=Show log button on taskbar (Win)
|
||||||
|
sebserver.examconfig.props.label.allowedDisplaysIgnoreFailure=Ignore errors when validating display configuration.
|
||||||
|
sebserver.examconfig.props.label.allowedDisplaysIgnoreFailure.tooltip=Needs to be active when using SEB inside a virtual machine
|
||||||
|
|
||||||
sebserver.examconfig.props.group.registry=While running SEB
|
sebserver.examconfig.props.group.registry=While running SEB
|
||||||
sebserver.examconfig.props.group.registry.tooltip=Options in the Windows Security Screen invoked by Ctrl-Alt-Del
|
sebserver.examconfig.props.group.registry.tooltip=Options in the Windows Security Screen invoked by Ctrl-Alt-Del
|
||||||
|
@ -1628,7 +1633,7 @@ sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall
|
||||||
sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall
|
sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall
|
||||||
sebserver.monitoring.exam.action.proctoring.closeTownhall=Close Townhall
|
sebserver.monitoring.exam.action.proctoring.closeTownhall=Close Townhall
|
||||||
|
|
||||||
sebserver.monitoring.exam.proctoring.room.all.name=Exam Room
|
sebserver.monitoring.exam.proctoring.room.all.name=Townhall Room
|
||||||
sebserver.monitoring.exam.proctoring.action.close=Close Window
|
sebserver.monitoring.exam.proctoring.action.close=Close Window
|
||||||
sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast
|
sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast
|
||||||
sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast
|
sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast
|
||||||
|
@ -1674,6 +1679,7 @@ sebserver.monitoring.search.action=Search
|
||||||
sebserver.monitoring.search.list.empty=No Client Connections available
|
sebserver.monitoring.search.list.empty=No Client Connections available
|
||||||
sebserver.monitoring.search.list.name=Session or User Name
|
sebserver.monitoring.search.list.name=Session or User Name
|
||||||
sebserver.monitoring.search.list.ip=IP Address
|
sebserver.monitoring.search.list.ip=IP Address
|
||||||
|
sebserver.monitoring.search.list.status=Status
|
||||||
|
|
||||||
|
|
||||||
sebserver.monitoring.exam.connection.emptySelection=At first please select a Connection from the list
|
sebserver.monitoring.exam.connection.emptySelection=At first please select a Connection from the list
|
||||||
|
|
|
@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService
|
||||||
public class ZoomWindowScriptResolverTest {
|
public class ZoomWindowScriptResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJitsiWindowScriptResolver() {
|
public void testZoomWindowScriptResolver() {
|
||||||
final DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
|
final DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
|
||||||
final Resource resource = defaultResourceLoader.getResource(ZoomWindowScriptResolver.RES_PATH);
|
final Resource resource = defaultResourceLoader.getResource(ZoomWindowScriptResolver.RES_PATH);
|
||||||
final ZoomWindowScriptResolver zoomWindowScriptResolver = new ZoomWindowScriptResolver(resource);
|
final ZoomWindowScriptResolver zoomWindowScriptResolver = new ZoomWindowScriptResolver(resource);
|
||||||
|
|
Loading…
Add table
Reference in a new issue