finished GUI refactoring
This commit is contained in:
parent
000e8c3c7d
commit
26561288c9
31 changed files with 7533 additions and 7178 deletions
|
@ -1,287 +1,294 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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.gbl.model.exam;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
public final class QuizData implements GrantEntity {
|
||||
|
||||
public static final String FILTER_ATTR_START_TIME = "start_timestamp";
|
||||
|
||||
public static final String ATTR_ADDITIONAL_ATTRIBUTES = "ADDITIONAL_ATTRIBUTES";
|
||||
|
||||
public static final String QUIZ_ATTR_ID = "quiz_id";
|
||||
public static final String QUIZ_ATTR_INSTITUION_ID = Domain.EXAM.ATTR_INSTITUTION_ID;
|
||||
public static final String QUIZ_ATTR_LMS_SETUP_ID = "lms_setup_id";
|
||||
public static final String QUIZ_ATTR_LMS_TYPE = "lms_setup_type";
|
||||
public static final String QUIZ_ATTR_NAME = "quiz_name";
|
||||
public static final String QUIZ_ATTR_DESCRIPTION = "quiz_description";
|
||||
public static final String QUIZ_ATTR_START_TIME = "quiz_start_time";
|
||||
public static final String QUIZ_ATTR_END_TIME = "quiz_end_time";
|
||||
public static final String QUIZ_ATTR_START_URL = "quiz_start_url";
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_ID)
|
||||
public final String id;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_INSTITUION_ID)
|
||||
public final Long institutionId;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_LMS_SETUP_ID)
|
||||
public final Long lmsSetupId;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_LMS_TYPE)
|
||||
public final LmsType lmsType;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_NAME)
|
||||
public final String name;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_DESCRIPTION)
|
||||
public final String description;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_START_TIME)
|
||||
public final DateTime startTime;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_END_TIME)
|
||||
public final DateTime endTime;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_START_URL)
|
||||
public final String startURL;
|
||||
|
||||
@JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES)
|
||||
public final Map<String, String> additionalAttributes;
|
||||
|
||||
@JsonCreator
|
||||
public QuizData(
|
||||
@JsonProperty(QUIZ_ATTR_ID) final String id,
|
||||
@JsonProperty(QUIZ_ATTR_INSTITUION_ID) final Long institutionId,
|
||||
@JsonProperty(QUIZ_ATTR_LMS_SETUP_ID) final Long lmsSetupId,
|
||||
@JsonProperty(QUIZ_ATTR_LMS_TYPE) final LmsType lmsType,
|
||||
@JsonProperty(QUIZ_ATTR_NAME) final String name,
|
||||
@JsonProperty(QUIZ_ATTR_DESCRIPTION) final String description,
|
||||
@JsonProperty(QUIZ_ATTR_START_TIME) final DateTime startTime,
|
||||
@JsonProperty(QUIZ_ATTR_END_TIME) final DateTime endTime,
|
||||
@JsonProperty(QUIZ_ATTR_START_URL) final String startURL,
|
||||
@JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES) final Map<String, String> additionalAttributes) {
|
||||
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
this.lmsSetupId = lmsSetupId;
|
||||
this.lmsType = lmsType;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.startURL = startURL;
|
||||
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
||||
}
|
||||
|
||||
public QuizData(
|
||||
final String id,
|
||||
final Long institutionId,
|
||||
final Long lmsSetupId,
|
||||
final LmsType lmsType,
|
||||
final String name,
|
||||
final String description,
|
||||
final String startTime,
|
||||
final String endTime,
|
||||
final String startURL) {
|
||||
|
||||
this(id, institutionId, lmsSetupId, lmsType, name, description,
|
||||
startTime, endTime, startURL, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public QuizData(
|
||||
final String id,
|
||||
final Long institutionId,
|
||||
final Long lmsSetupId,
|
||||
final LmsType lmsType,
|
||||
final String name,
|
||||
final String description,
|
||||
final String startTime,
|
||||
final String endTime,
|
||||
final String startURL,
|
||||
final Map<String, String> additionalAttributes) {
|
||||
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
this.lmsSetupId = lmsSetupId;
|
||||
this.lmsType = lmsType;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.startTime = (startTime != null)
|
||||
? LocalDateTime
|
||||
.parse(startTime, Constants.STANDARD_DATE_TIME_FORMATTER)
|
||||
.toDateTime(DateTimeZone.UTC)
|
||||
: null;
|
||||
this.endTime = (endTime != null)
|
||||
? LocalDateTime
|
||||
.parse(endTime, Constants.STANDARD_DATE_TIME_FORMATTER)
|
||||
.toDateTime(DateTimeZone.UTC)
|
||||
: null;
|
||||
this.startURL = startURL;
|
||||
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelId() {
|
||||
if (this.id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.valueOf(this.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType entityType() {
|
||||
return EntityType.EXAM;
|
||||
}
|
||||
|
||||
public String geId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getInstitutionId() {
|
||||
return this.institutionId;
|
||||
}
|
||||
|
||||
public Long getLmsSetupId() {
|
||||
return this.lmsSetupId;
|
||||
}
|
||||
|
||||
public LmsType getLmsType() {
|
||||
return this.lmsType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public DateTime getStartTime() {
|
||||
return this.startTime;
|
||||
}
|
||||
|
||||
public DateTime getEndTime() {
|
||||
return this.endTime;
|
||||
}
|
||||
|
||||
public String getStartURL() {
|
||||
return this.startURL;
|
||||
}
|
||||
|
||||
public Map<String, String> getAdditionalAttributes() {
|
||||
return this.additionalAttributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("QuizData [id=");
|
||||
builder.append(this.id);
|
||||
builder.append(", institutionId=");
|
||||
builder.append(this.institutionId);
|
||||
builder.append(", lmsSetupId=");
|
||||
builder.append(this.lmsSetupId);
|
||||
builder.append(", lmsType=");
|
||||
builder.append(this.lmsType);
|
||||
builder.append(", name=");
|
||||
builder.append(this.name);
|
||||
builder.append(", description=");
|
||||
builder.append(this.description);
|
||||
builder.append(", startTime=");
|
||||
builder.append(this.startTime);
|
||||
builder.append(", endTime=");
|
||||
builder.append(this.endTime);
|
||||
builder.append(", startURL=");
|
||||
builder.append(this.startURL);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getIdComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.id == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.id == null)
|
||||
? -1
|
||||
: qd1.id.compareTo(qd2.id))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getNameComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.name == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.name == null)
|
||||
? -1
|
||||
: qd1.name.compareTo(qd2.name))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getStartTimeComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.startTime == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.startTime == null)
|
||||
? -1
|
||||
: qd1.startTime.compareTo(qd2.startTime))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getEndTimeComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.endTime == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.endTime == null)
|
||||
? -1
|
||||
: qd1.endTime.compareTo(qd2.endTime))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getComparator(final String sort) {
|
||||
final boolean descending = PageSortOrder.getSortOrder(sort) == PageSortOrder.DESCENDING;
|
||||
final String sortParam = PageSortOrder.decode(sort);
|
||||
if (QUIZ_ATTR_NAME.equals(sortParam)) {
|
||||
return getNameComparator(descending);
|
||||
} else if (QUIZ_ATTR_START_TIME.equals(sortParam)) {
|
||||
return getStartTimeComparator(descending);
|
||||
} else if (QUIZ_ATTR_END_TIME.equals(sortParam)) {
|
||||
return getEndTimeComparator(descending);
|
||||
}
|
||||
|
||||
return getIdComparator(descending);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2018 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.gbl.model.exam;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
public final class QuizData implements GrantEntity {
|
||||
|
||||
public static final String FILTER_ATTR_START_TIME = "start_timestamp";
|
||||
|
||||
public static final String ATTR_ADDITIONAL_ATTRIBUTES = "ADDITIONAL_ATTRIBUTES";
|
||||
|
||||
public static final String QUIZ_ATTR_ID = "quiz_id";
|
||||
public static final String QUIZ_ATTR_INSTITUTION_ID = Domain.EXAM.ATTR_INSTITUTION_ID;
|
||||
public static final String QUIZ_ATTR_LMS_SETUP_ID = "lms_setup_id";
|
||||
public static final String QUIZ_ATTR_LMS_TYPE = "lms_setup_type";
|
||||
public static final String QUIZ_ATTR_NAME = "quiz_name";
|
||||
public static final String QUIZ_ATTR_DESCRIPTION = "quiz_description";
|
||||
public static final String QUIZ_ATTR_START_TIME = "quiz_start_time";
|
||||
public static final String QUIZ_ATTR_END_TIME = "quiz_end_time";
|
||||
public static final String QUIZ_ATTR_START_URL = "quiz_start_url";
|
||||
|
||||
public static final String ATTR_ADDITIONAL_CREATION_TIME = "time_created";
|
||||
public static final String ATTR_ADDITIONAL_SHORT_NAME = "course_short_name";
|
||||
public static final String ATTR_ADDITIONAL_FULL_NAME = "course_full_name";
|
||||
public static final String ATTR_ADDITIONAL_DISPLAY_NAME = "course_display_name";
|
||||
public static final String ATTR_ADDITIONAL_SUMMARY = "course_summary";
|
||||
public static final String ATTR_ADDITIONAL_TIME_LIMIT = "time_limit";
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_ID)
|
||||
public final String id;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_INSTITUTION_ID)
|
||||
public final Long institutionId;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_LMS_SETUP_ID)
|
||||
public final Long lmsSetupId;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_LMS_TYPE)
|
||||
public final LmsType lmsType;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_NAME)
|
||||
public final String name;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_DESCRIPTION)
|
||||
public final String description;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_START_TIME)
|
||||
public final DateTime startTime;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_END_TIME)
|
||||
public final DateTime endTime;
|
||||
|
||||
@JsonProperty(QUIZ_ATTR_START_URL)
|
||||
public final String startURL;
|
||||
|
||||
@JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES)
|
||||
public final Map<String, String> additionalAttributes;
|
||||
|
||||
@JsonCreator
|
||||
public QuizData(
|
||||
@JsonProperty(QUIZ_ATTR_ID) final String id,
|
||||
@JsonProperty(QUIZ_ATTR_INSTITUTION_ID) final Long institutionId,
|
||||
@JsonProperty(QUIZ_ATTR_LMS_SETUP_ID) final Long lmsSetupId,
|
||||
@JsonProperty(QUIZ_ATTR_LMS_TYPE) final LmsType lmsType,
|
||||
@JsonProperty(QUIZ_ATTR_NAME) final String name,
|
||||
@JsonProperty(QUIZ_ATTR_DESCRIPTION) final String description,
|
||||
@JsonProperty(QUIZ_ATTR_START_TIME) final DateTime startTime,
|
||||
@JsonProperty(QUIZ_ATTR_END_TIME) final DateTime endTime,
|
||||
@JsonProperty(QUIZ_ATTR_START_URL) final String startURL,
|
||||
@JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES) final Map<String, String> additionalAttributes) {
|
||||
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
this.lmsSetupId = lmsSetupId;
|
||||
this.lmsType = lmsType;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.startURL = startURL;
|
||||
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
||||
}
|
||||
|
||||
public QuizData(
|
||||
final String id,
|
||||
final Long institutionId,
|
||||
final Long lmsSetupId,
|
||||
final LmsType lmsType,
|
||||
final String name,
|
||||
final String description,
|
||||
final String startTime,
|
||||
final String endTime,
|
||||
final String startURL) {
|
||||
|
||||
this(id, institutionId, lmsSetupId, lmsType, name, description,
|
||||
startTime, endTime, startURL, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public QuizData(
|
||||
final String id,
|
||||
final Long institutionId,
|
||||
final Long lmsSetupId,
|
||||
final LmsType lmsType,
|
||||
final String name,
|
||||
final String description,
|
||||
final String startTime,
|
||||
final String endTime,
|
||||
final String startURL,
|
||||
final Map<String, String> additionalAttributes) {
|
||||
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
this.lmsSetupId = lmsSetupId;
|
||||
this.lmsType = lmsType;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.startTime = (startTime != null)
|
||||
? LocalDateTime
|
||||
.parse(startTime, Constants.STANDARD_DATE_TIME_FORMATTER)
|
||||
.toDateTime(DateTimeZone.UTC)
|
||||
: null;
|
||||
this.endTime = (endTime != null)
|
||||
? LocalDateTime
|
||||
.parse(endTime, Constants.STANDARD_DATE_TIME_FORMATTER)
|
||||
.toDateTime(DateTimeZone.UTC)
|
||||
: null;
|
||||
this.startURL = startURL;
|
||||
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelId() {
|
||||
if (this.id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.valueOf(this.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType entityType() {
|
||||
return EntityType.EXAM;
|
||||
}
|
||||
|
||||
public String geId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getInstitutionId() {
|
||||
return this.institutionId;
|
||||
}
|
||||
|
||||
public Long getLmsSetupId() {
|
||||
return this.lmsSetupId;
|
||||
}
|
||||
|
||||
public LmsType getLmsType() {
|
||||
return this.lmsType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public DateTime getStartTime() {
|
||||
return this.startTime;
|
||||
}
|
||||
|
||||
public DateTime getEndTime() {
|
||||
return this.endTime;
|
||||
}
|
||||
|
||||
public String getStartURL() {
|
||||
return this.startURL;
|
||||
}
|
||||
|
||||
public Map<String, String> getAdditionalAttributes() {
|
||||
return this.additionalAttributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("QuizData [id=");
|
||||
builder.append(this.id);
|
||||
builder.append(", institutionId=");
|
||||
builder.append(this.institutionId);
|
||||
builder.append(", lmsSetupId=");
|
||||
builder.append(this.lmsSetupId);
|
||||
builder.append(", lmsType=");
|
||||
builder.append(this.lmsType);
|
||||
builder.append(", name=");
|
||||
builder.append(this.name);
|
||||
builder.append(", description=");
|
||||
builder.append(this.description);
|
||||
builder.append(", startTime=");
|
||||
builder.append(this.startTime);
|
||||
builder.append(", endTime=");
|
||||
builder.append(this.endTime);
|
||||
builder.append(", startURL=");
|
||||
builder.append(this.startURL);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getIdComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.id == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.id == null)
|
||||
? -1
|
||||
: qd1.id.compareTo(qd2.id))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getNameComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.name == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.name == null)
|
||||
? -1
|
||||
: qd1.name.compareTo(qd2.name))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getStartTimeComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.startTime == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.startTime == null)
|
||||
? -1
|
||||
: qd1.startTime.compareTo(qd2.startTime))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getEndTimeComparator(final boolean descending) {
|
||||
return (qd1, qd2) -> ((qd1 == qd2)
|
||||
? 0
|
||||
: (qd1 == null || qd1.endTime == null)
|
||||
? 1
|
||||
: (qd2 == null || qd2.endTime == null)
|
||||
? -1
|
||||
: qd1.endTime.compareTo(qd2.endTime))
|
||||
* ((descending) ? -1 : 1);
|
||||
}
|
||||
|
||||
public static Comparator<QuizData> getComparator(final String sort) {
|
||||
final boolean descending = PageSortOrder.getSortOrder(sort) == PageSortOrder.DESCENDING;
|
||||
final String sortParam = PageSortOrder.decode(sort);
|
||||
if (QUIZ_ATTR_NAME.equals(sortParam)) {
|
||||
return getNameComparator(descending);
|
||||
} else if (QUIZ_ATTR_START_TIME.equals(sortParam)) {
|
||||
return getStartTimeComparator(descending);
|
||||
} else if (QUIZ_ATTR_END_TIME.equals(sortParam)) {
|
||||
return getEndTimeComparator(descending);
|
||||
}
|
||||
|
||||
return getIdComparator(descending);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -332,7 +332,9 @@ public final class Utils {
|
|||
return null;
|
||||
}
|
||||
|
||||
return text.replace("</br>", "\n");
|
||||
return text
|
||||
.replace("<br/>", "\n")
|
||||
.replace("<br></br>", "\n");
|
||||
}
|
||||
|
||||
public static String encodeFormURL_UTF_8(final String value) {
|
||||
|
|
|
@ -1,231 +1,231 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.commons.codec.binary.Base64InputStream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
||||
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
private static final String INST_SUFFIX_ATTRIBUTE = "instSuffix";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(InstitutionalAuthenticationEntryPoint.class);
|
||||
|
||||
private final String guiEntryPoint;
|
||||
private final String defaultLogo;
|
||||
private final WebserviceURIService webserviceURIService;
|
||||
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||
|
||||
protected InstitutionalAuthenticationEntryPoint(
|
||||
@Value("${sebserver.gui.entrypoint}") final String guiEntryPoint,
|
||||
@Value("${sebserver.gui.defaultLogo:" + Constants.NO_NAME + "}") final String defaultLogoFileName,
|
||||
final WebserviceURIService webserviceURIService,
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||
final ResourceLoader resourceLoader) {
|
||||
|
||||
this.guiEntryPoint = guiEntryPoint;
|
||||
this.webserviceURIService = webserviceURIService;
|
||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||
|
||||
String _defaultLogo = null;
|
||||
if (!Constants.NO_NAME.equals(defaultLogoFileName)) {
|
||||
try {
|
||||
|
||||
final String extension = ImageUploadSelection.SUPPORTED_IMAGE_FILES.stream()
|
||||
.filter(ext -> defaultLogoFileName.endsWith(ext))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (extension == null) {
|
||||
throw new IllegalArgumentException("Image of type: " + defaultLogoFileName + " not supported");
|
||||
}
|
||||
|
||||
final Resource resource = resourceLoader.getResource(defaultLogoFileName);
|
||||
final Reader reader = new InputStreamReader(
|
||||
new Base64InputStream(resource.getInputStream(), true),
|
||||
Charsets.UTF_8);
|
||||
|
||||
_defaultLogo = FileCopyUtils.copyToString(reader);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to load default logo image from filesystem: {}", defaultLogoFileName);
|
||||
_defaultLogo = null;
|
||||
}
|
||||
|
||||
this.defaultLogo = _defaultLogo;
|
||||
} else {
|
||||
this.defaultLogo = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response,
|
||||
final AuthenticationException authException) throws IOException, ServletException {
|
||||
|
||||
final String institutionalEndpoint = extractInstitutionalEndpoint(request);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
|
||||
}
|
||||
|
||||
final String logoImageBase64 = requestLogoImage(institutionalEndpoint);
|
||||
if (StringUtils.isNotBlank(logoImageBase64)) {
|
||||
request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64);
|
||||
request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, institutionalEndpoint);
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint);
|
||||
} else {
|
||||
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
private void forwardToEntryPoint(
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response,
|
||||
final String entryPoint) throws ServletException, IOException {
|
||||
|
||||
final RequestDispatcher dispatcher = request
|
||||
.getServletContext()
|
||||
.getRequestDispatcher(entryPoint);
|
||||
|
||||
dispatcher.forward(request, response);
|
||||
}
|
||||
|
||||
public static String extractInstitutionalEndpoint(final HttpServletRequest request) {
|
||||
final String requestURI = request.getRequestURI();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Trying to verify insitution from requested entrypoint url: {}", requestURI);
|
||||
}
|
||||
|
||||
try {
|
||||
return requestURI.substring(
|
||||
requestURI.lastIndexOf(Constants.SLASH) + 1,
|
||||
requestURI.length());
|
||||
} catch (final Exception e) {
|
||||
log.error("Fauled to extract institutional URL suffix: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String extractInstitutionalEndpoint() {
|
||||
try {
|
||||
final Object attribute = RWT.getUISession().getHttpSession().getAttribute(INST_SUFFIX_ATTRIBUTE);
|
||||
return (attribute != null) ? String.valueOf(attribute) : null;
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to extract institutional endpoint form user session: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String requestLogoImage(final String institutionalEndpoint) {
|
||||
if (StringUtils.isBlank(institutionalEndpoint)) {
|
||||
return this.defaultLogo;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
||||
.getClientHttpRequestFactory()
|
||||
.getOrThrow();
|
||||
|
||||
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
||||
|
||||
final ResponseEntity<String> exchange = restTemplate
|
||||
.exchange(
|
||||
this.webserviceURIService.getURIBuilder()
|
||||
.path(API.INFO_ENDPOINT + API.INSTITUTIONAL_LOGO_PATH)
|
||||
.toUriString(),
|
||||
HttpMethod.GET,
|
||||
HttpEntity.EMPTY,
|
||||
String.class,
|
||||
institutionalEndpoint);
|
||||
|
||||
if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) {
|
||||
return exchange.getBody();
|
||||
} else {
|
||||
log.warn("Failed to verify insitution from requested entrypoint url: {}, response: {}",
|
||||
institutionalEndpoint,
|
||||
exchange);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to verify insitution from requested entrypoint url: {}",
|
||||
institutionalEndpoint,
|
||||
e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** TODO this seems not to work as expected. Different Theme is only possible in RAP on different
|
||||
* entry-points and since entry-points are statically defined within the RAPConficuration
|
||||
* there is no possibility to apply them dynamically within an institution so far.
|
||||
*
|
||||
* @param institutionalEndpoint
|
||||
* @return */
|
||||
// private boolean initInstitutionalBasedThemeEntryPoint(final String institutionalEndpoint) {
|
||||
// try {
|
||||
// final ApplicationContextImpl appContext = (ApplicationContextImpl) RWT.getApplicationContext();
|
||||
// final Map<String, String> properties = new HashMap<>();
|
||||
// properties.put(WebClient.THEME_ID, "sms");
|
||||
// appContext.getEntryPointManager().register(
|
||||
// institutionalEndpoint,
|
||||
// new RAPSpringEntryPointFactory(),
|
||||
// properties);
|
||||
//
|
||||
// return true;
|
||||
// } catch (final Exception e) {
|
||||
// log.warn("Failed to dynamically set entry point for institution: {}", institutionalEndpoint, e);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.commons.codec.binary.Base64InputStream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
||||
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
private static final String INST_SUFFIX_ATTRIBUTE = "instSuffix";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(InstitutionalAuthenticationEntryPoint.class);
|
||||
|
||||
private final String guiEntryPoint;
|
||||
private final String defaultLogo;
|
||||
private final WebserviceURIService webserviceURIService;
|
||||
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||
|
||||
protected InstitutionalAuthenticationEntryPoint(
|
||||
@Value("${sebserver.gui.entrypoint}") final String guiEntryPoint,
|
||||
@Value("${sebserver.gui.defaultLogo:" + Constants.NO_NAME + "}") final String defaultLogoFileName,
|
||||
final WebserviceURIService webserviceURIService,
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||
final ResourceLoader resourceLoader) {
|
||||
|
||||
this.guiEntryPoint = guiEntryPoint;
|
||||
this.webserviceURIService = webserviceURIService;
|
||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||
|
||||
String _defaultLogo = null;
|
||||
if (!Constants.NO_NAME.equals(defaultLogoFileName)) {
|
||||
try {
|
||||
|
||||
final String extension = ImageUploadSelection.SUPPORTED_IMAGE_FILES.stream()
|
||||
.filter(ext -> defaultLogoFileName.endsWith(ext))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (extension == null) {
|
||||
throw new IllegalArgumentException("Image of type: " + defaultLogoFileName + " not supported");
|
||||
}
|
||||
|
||||
final Resource resource = resourceLoader.getResource(defaultLogoFileName);
|
||||
final Reader reader = new InputStreamReader(
|
||||
new Base64InputStream(resource.getInputStream(), true),
|
||||
Charsets.UTF_8);
|
||||
|
||||
_defaultLogo = FileCopyUtils.copyToString(reader);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to load default logo image from filesystem: {}", defaultLogoFileName);
|
||||
_defaultLogo = null;
|
||||
}
|
||||
|
||||
this.defaultLogo = _defaultLogo;
|
||||
} else {
|
||||
this.defaultLogo = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response,
|
||||
final AuthenticationException authException) throws IOException, ServletException {
|
||||
|
||||
final String institutionalEndpoint = extractInstitutionalEndpoint(request);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
|
||||
}
|
||||
|
||||
final String logoImageBase64 = requestLogoImage(institutionalEndpoint);
|
||||
if (StringUtils.isNotBlank(logoImageBase64)) {
|
||||
request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64);
|
||||
request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, institutionalEndpoint);
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint);
|
||||
} else {
|
||||
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
private void forwardToEntryPoint(
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response,
|
||||
final String entryPoint) throws ServletException, IOException {
|
||||
|
||||
final RequestDispatcher dispatcher = request
|
||||
.getServletContext()
|
||||
.getRequestDispatcher(entryPoint);
|
||||
|
||||
dispatcher.forward(request, response);
|
||||
}
|
||||
|
||||
public static String extractInstitutionalEndpoint(final HttpServletRequest request) {
|
||||
final String requestURI = request.getRequestURI();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Trying to verify institution from requested entrypoint url: {}", requestURI);
|
||||
}
|
||||
|
||||
try {
|
||||
return requestURI.substring(
|
||||
requestURI.lastIndexOf(Constants.SLASH) + 1,
|
||||
requestURI.length());
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to extract institutional URL suffix: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String extractInstitutionalEndpoint() {
|
||||
try {
|
||||
final Object attribute = RWT.getUISession().getHttpSession().getAttribute(INST_SUFFIX_ATTRIBUTE);
|
||||
return (attribute != null) ? String.valueOf(attribute) : null;
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to extract institutional endpoint form user session: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String requestLogoImage(final String institutionalEndpoint) {
|
||||
if (StringUtils.isBlank(institutionalEndpoint)) {
|
||||
return this.defaultLogo;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
||||
.getClientHttpRequestFactory()
|
||||
.getOrThrow();
|
||||
|
||||
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
||||
|
||||
final ResponseEntity<String> exchange = restTemplate
|
||||
.exchange(
|
||||
this.webserviceURIService.getURIBuilder()
|
||||
.path(API.INFO_ENDPOINT + API.INSTITUTIONAL_LOGO_PATH)
|
||||
.toUriString(),
|
||||
HttpMethod.GET,
|
||||
HttpEntity.EMPTY,
|
||||
String.class,
|
||||
institutionalEndpoint);
|
||||
|
||||
if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) {
|
||||
return exchange.getBody();
|
||||
} else {
|
||||
log.warn("Failed to verify insitution from requested entrypoint url: {}, response: {}",
|
||||
institutionalEndpoint,
|
||||
exchange);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to verify insitution from requested entrypoint url: {}",
|
||||
institutionalEndpoint,
|
||||
e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** TODO this seems not to work as expected. Different Theme is only possible in RAP on different
|
||||
* entry-points and since entry-points are statically defined within the RAPConficuration
|
||||
* there is no possibility to apply them dynamically within an institution so far.
|
||||
*
|
||||
* @param institutionalEndpoint
|
||||
* @return */
|
||||
// private boolean initInstitutionalBasedThemeEntryPoint(final String institutionalEndpoint) {
|
||||
// try {
|
||||
// final ApplicationContextImpl appContext = (ApplicationContextImpl) RWT.getApplicationContext();
|
||||
// final Map<String, String> properties = new HashMap<>();
|
||||
// properties.put(WebClient.THEME_ID, "sms");
|
||||
// appContext.getEntryPointManager().register(
|
||||
// institutionalEndpoint,
|
||||
// new RAPSpringEntryPointFactory(),
|
||||
// properties);
|
||||
//
|
||||
// return true;
|
||||
// } catch (final Exception e) {
|
||||
// log.warn("Failed to dynamically set entry point for institution: {}", institutionalEndpoint, e);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
@ -117,6 +118,8 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
private final static LocTextKey CONFIG_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.title");
|
||||
private final static LocTextKey CONFIG_LIST_TITLE_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private final static LocTextKey CONFIG_NAME_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.name");
|
||||
private final static LocTextKey CONFIG_DESCRIPTION_COLUMN_KEY =
|
||||
|
@ -128,6 +131,8 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.title");
|
||||
private final static LocTextKey INDICATOR_LIST_TITLE_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private final static LocTextKey INDICATOR_TYPE_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.type");
|
||||
private final static LocTextKey INDICATOR_NAME_COLUMN_KEY =
|
||||
|
@ -326,11 +331,12 @@ public class ExamForm implements TemplateComposer {
|
|||
.addField(FormBuilder.singleSelection(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
String.valueOf(exam.type),
|
||||
(exam.type != null) ? String.valueOf(exam.type) : Exam.ExamType.UNDEFINED.name(),
|
||||
this.resourceService::examTypeResources)
|
||||
.withLabelSpan(2)
|
||||
.withInputSpan(4)
|
||||
.withEmptyCellSpan(2))
|
||||
.withEmptyCellSpan(2)
|
||||
.mandatory(!readonly))
|
||||
|
||||
.addField(FormBuilder.multiComboSelection(
|
||||
Domain.EXAM.ATTR_SUPPORTER,
|
||||
|
@ -339,7 +345,8 @@ public class ExamForm implements TemplateComposer {
|
|||
this.resourceService::examSupporterResources)
|
||||
.withLabelSpan(2)
|
||||
.withInputSpan(4)
|
||||
.withEmptyCellSpan(2))
|
||||
.withEmptyCellSpan(2)
|
||||
.mandatory(!readonly))
|
||||
|
||||
.buildFor(importFromQuizData
|
||||
? this.restService.getRestCall(ImportAsExam.class)
|
||||
|
@ -398,7 +405,8 @@ public class ExamForm implements TemplateComposer {
|
|||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
CONFIG_LIST_TITLE_KEY);
|
||||
CONFIG_LIST_TITLE_KEY,
|
||||
CONFIG_LIST_TITLE_TOOLTIP_KEY);
|
||||
this.widgetFactory.labelSeparator(content);
|
||||
|
||||
final EntityTable<ExamConfigurationMap> configurationTable =
|
||||
|
@ -428,6 +436,13 @@ public class ExamForm implements TemplateComposer {
|
|||
() -> modifyGrant,
|
||||
this::viewExamConfigPageAction)
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
|
||||
ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXPORT,
|
||||
ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
final EntityKey configMapKey = (configurationTable.hasAnyContent())
|
||||
|
@ -447,7 +462,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withEntityKey(configMapKey)
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent())
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -461,7 +476,7 @@ public class ExamForm implements TemplateComposer {
|
|||
}
|
||||
return null;
|
||||
})
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable, false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
|
||||
.withParentEntityKey(entityKey)
|
||||
|
@ -470,7 +485,7 @@ public class ExamForm implements TemplateComposer {
|
|||
this::downloadExamConfigAction,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent())
|
||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
|
||||
.withSelect(
|
||||
|
@ -478,14 +493,15 @@ public class ExamForm implements TemplateComposer {
|
|||
this::getExamConfigKey,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
|
||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false);
|
||||
|
||||
// List of Indicators
|
||||
this.widgetFactory.label(content, "");
|
||||
this.widgetFactory.label(content, StringUtils.EMPTY);
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
INDICATOR_LIST_TITLE_KEY);
|
||||
INDICATOR_LIST_TITLE_KEY,
|
||||
INDICATOR_LIST_TITLE_TOOLTIP_KEY);
|
||||
this.widgetFactory.labelSeparator(content);
|
||||
|
||||
final EntityTable<Indicator> indicatorTable =
|
||||
|
@ -520,6 +536,11 @@ public class ExamForm implements TemplateComposer {
|
|||
.withParentEntityKey(entityKey)
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST,
|
||||
ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
|
@ -534,7 +555,7 @@ public class ExamForm implements TemplateComposer {
|
|||
indicatorTable::getSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent())
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -542,7 +563,7 @@ public class ExamForm implements TemplateComposer {
|
|||
indicatorTable::getSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent());
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -560,7 +581,7 @@ public class ExamForm implements TemplateComposer {
|
|||
processFormSave,
|
||||
true,
|
||||
this.restService,
|
||||
t -> log.error("Failed to intially restrict the course for SEB on LMS: {}", t.getMessage()));
|
||||
t -> log.error("Failed to initially restrict the course for SEB on LMS: {}", t.getMessage()));
|
||||
}
|
||||
|
||||
return processFormSave;
|
||||
|
@ -589,7 +610,7 @@ public class ExamForm implements TemplateComposer {
|
|||
result
|
||||
.stream()
|
||||
.map(message -> this.consistencyMessageMapping.get(message.messageCode))
|
||||
.filter(message -> message != null)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(message -> this.widgetFactory.labelLocalized(
|
||||
warningPanel,
|
||||
CustomVariant.MESSAGE,
|
||||
|
@ -699,7 +720,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withQueryParam(QuizData.QUIZ_ATTR_LMS_SETUP_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.map(quizzData -> new Exam(quizzData))
|
||||
.map(Exam::new)
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error));
|
||||
}
|
||||
|
||||
|
@ -735,7 +756,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.append(")")
|
||||
.append("</span>")
|
||||
.append(" | "),
|
||||
(sb1, sb2) -> sb1.append(sb2));
|
||||
StringBuilder::append);
|
||||
builder.delete(builder.length() - 3, builder.length() - 1);
|
||||
return builder.toString();
|
||||
}
|
||||
|
|
|
@ -1,279 +1,277 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ExamList implements TemplateComposer {
|
||||
|
||||
static final String EXAM_LIST_COLUMN_STARTTIME =
|
||||
"sebserver.exam.list.column.starttime";
|
||||
static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.list.title");
|
||||
static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
|
||||
new LocTextKey("sebserver.exam.list.action.no.modify.privilege");
|
||||
final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.info.pleaseSelect");
|
||||
final static LocTextKey COLUMN_TITLE_INSTITUTION_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.institution");
|
||||
final static LocTextKey COLUMN_TITLE_LMS_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.lmssetup");
|
||||
final static LocTextKey COLUMN_TITLE_NAME_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.name");
|
||||
final static LocTextKey COLUMN_TITLE_TYPE_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.type");
|
||||
final static LocTextKey NO_MODIFY_OF_OUT_DATED_EXAMS =
|
||||
new LocTextKey("sebserver.exam.list.modify.out.dated");
|
||||
final static LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.list.empty");
|
||||
|
||||
private final TableFilterAttribute institutionFilter;
|
||||
private final TableFilterAttribute lmsFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
protected ExamList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.institutionFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Entity.FILTER_ATTR_INSTITUTION,
|
||||
this.resourceService::institutionResource);
|
||||
|
||||
this.lmsFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
LmsSetup.FILTER_ATTR_LMS_SETUP,
|
||||
this.resourceService::lmsSetupResource);
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_TYPE,
|
||||
this.resourceService::examTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
PAGE_TITLE_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final BooleanSupplier isSebAdmin =
|
||||
() -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
|
||||
final Function<String, String> institutionNameFunction =
|
||||
this.resourceService.getInstitutionNameFunction();
|
||||
|
||||
// table
|
||||
final EntityTable<Exam> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRowDecorator(decorateOnExamConsistency(this.pageService))
|
||||
|
||||
.withStaticFilter(Exam.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
|
||||
|
||||
.withColumnIf(
|
||||
isSebAdmin,
|
||||
() -> new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_INSTITUTION_ID,
|
||||
COLUMN_TITLE_INSTITUTION_KEY,
|
||||
exam -> institutionNameFunction
|
||||
.apply(String.valueOf(exam.getInstitutionId())))
|
||||
.withFilter(this.institutionFilter))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_LMS_SETUP_ID,
|
||||
COLUMN_TITLE_LMS_KEY,
|
||||
examLmsSetupNameFunction(this.resourceService))
|
||||
.withFilter(this.lmsFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
new LocTextKey(
|
||||
EXAM_LIST_COLUMN_STARTTIME,
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getStartTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE,
|
||||
QuizData.FILTER_ATTR_START_TIME,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
COLUMN_TITLE_TYPE_KEY,
|
||||
this.resourceService::localizedExamTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.create())
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
|
||||
actionBuilder
|
||||
|
||||
// Removed as discussed in SEBSERV-52
|
||||
// .newAction(ActionDefinition.EXAM_IMPORT)
|
||||
// .publishIf(userGrant::im)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(table::hasAnyContent)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST)
|
||||
.withSelect(
|
||||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
|
||||
action -> modifyExam(action, table),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && table.hasAnyContent());
|
||||
|
||||
}
|
||||
|
||||
static final PageAction modifyExam(final PageAction action, final EntityTable<Exam> table) {
|
||||
final Exam exam = table.getSingleSelectedROWData();
|
||||
|
||||
if (exam == null) {
|
||||
throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
|
||||
}
|
||||
|
||||
if (exam.endTime != null) {
|
||||
final DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
if (exam.endTime.isBefore(now)) {
|
||||
throw new PageMessageException(NO_MODIFY_OF_OUT_DATED_EXAMS);
|
||||
}
|
||||
}
|
||||
|
||||
return action.withEntityKey(action.getSingleSelection());
|
||||
}
|
||||
|
||||
static final BiConsumer<TableItem, ExamConfigurationMap> decorateOnExamMapConsistency(
|
||||
final PageService pageService) {
|
||||
|
||||
return (item, examMap) -> {
|
||||
pageService.getRestService().getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examMap.examId))
|
||||
.call()
|
||||
.ifPresent(exam -> decorateOnExamConsistency(item, exam, pageService));
|
||||
};
|
||||
}
|
||||
|
||||
static final BiConsumer<TableItem, Exam> decorateOnExamConsistency(final PageService pageService) {
|
||||
return (item, exam) -> decorateOnExamConsistency(item, exam, pageService);
|
||||
}
|
||||
|
||||
static final void decorateOnExamConsistency(
|
||||
final TableItem item,
|
||||
final Exam exam,
|
||||
final PageService pageService) {
|
||||
|
||||
if (exam.getStatus() != ExamStatus.RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
pageService.getRestService().getBuilder(CheckExamConsistency.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
|
||||
.call()
|
||||
.ifPresent(warnings -> {
|
||||
if (warnings != null && !warnings.isEmpty()) {
|
||||
item.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Function<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
|
||||
return exam -> resourceService.getLmsSetupNameFunction()
|
||||
.apply(String.valueOf(exam.lmsSetupId));
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ExamList implements TemplateComposer {
|
||||
|
||||
static final String EXAM_LIST_COLUMN_STARTTIME =
|
||||
"sebserver.exam.list.column.starttime";
|
||||
static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.list.title");
|
||||
static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
|
||||
new LocTextKey("sebserver.exam.list.action.no.modify.privilege");
|
||||
final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.info.pleaseSelect");
|
||||
final static LocTextKey COLUMN_TITLE_INSTITUTION_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.institution");
|
||||
final static LocTextKey COLUMN_TITLE_LMS_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.lmssetup");
|
||||
final static LocTextKey COLUMN_TITLE_NAME_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.name");
|
||||
final static LocTextKey COLUMN_TITLE_TYPE_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.type");
|
||||
final static LocTextKey NO_MODIFY_OF_OUT_DATED_EXAMS =
|
||||
new LocTextKey("sebserver.exam.list.modify.out.dated");
|
||||
final static LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.list.empty");
|
||||
|
||||
private final TableFilterAttribute institutionFilter;
|
||||
private final TableFilterAttribute lmsFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
protected ExamList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.institutionFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Entity.FILTER_ATTR_INSTITUTION,
|
||||
this.resourceService::institutionResource);
|
||||
|
||||
this.lmsFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
LmsSetup.FILTER_ATTR_LMS_SETUP,
|
||||
this.resourceService::lmsSetupResource);
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_TYPE,
|
||||
this.resourceService::examTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
PAGE_TITLE_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final BooleanSupplier isSebAdmin =
|
||||
() -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
|
||||
final Function<String, String> institutionNameFunction =
|
||||
this.resourceService.getInstitutionNameFunction();
|
||||
|
||||
// table
|
||||
final EntityTable<Exam> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRowDecorator(decorateOnExamConsistency(this.pageService))
|
||||
|
||||
.withStaticFilter(Exam.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
|
||||
|
||||
.withColumnIf(
|
||||
isSebAdmin,
|
||||
() -> new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_INSTITUTION_ID,
|
||||
COLUMN_TITLE_INSTITUTION_KEY,
|
||||
exam -> institutionNameFunction
|
||||
.apply(String.valueOf(exam.getInstitutionId())))
|
||||
.withFilter(this.institutionFilter))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_LMS_SETUP_ID,
|
||||
COLUMN_TITLE_LMS_KEY,
|
||||
examLmsSetupNameFunction(this.resourceService))
|
||||
.withFilter(this.lmsFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
new LocTextKey(
|
||||
EXAM_LIST_COLUMN_STARTTIME,
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getStartTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE,
|
||||
QuizData.FILTER_ATTR_START_TIME,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
COLUMN_TITLE_TYPE_KEY,
|
||||
this.resourceService::localizedExamTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_VIEW_FROM_LIST,
|
||||
ActionDefinition.EXAM_MODIFY_FROM_LIST))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(table::hasAnyContent, false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST)
|
||||
.withSelect(
|
||||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
|
||||
action -> modifyExam(action, table),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && table.hasAnyContent(), false);
|
||||
|
||||
}
|
||||
|
||||
static PageAction modifyExam(final PageAction action, final EntityTable<Exam> table) {
|
||||
final Exam exam = table.getSingleSelectedROWData();
|
||||
|
||||
if (exam == null) {
|
||||
throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
|
||||
}
|
||||
|
||||
if (exam.endTime != null) {
|
||||
final DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
if (exam.endTime.isBefore(now)) {
|
||||
throw new PageMessageException(NO_MODIFY_OF_OUT_DATED_EXAMS);
|
||||
}
|
||||
}
|
||||
|
||||
return action.withEntityKey(action.getSingleSelection());
|
||||
}
|
||||
|
||||
static BiConsumer<TableItem, ExamConfigurationMap> decorateOnExamMapConsistency(
|
||||
final PageService pageService) {
|
||||
|
||||
return (item, examMap) -> pageService.getRestService().getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examMap.examId))
|
||||
.call()
|
||||
.ifPresent(exam -> decorateOnExamConsistency(item, exam, pageService));
|
||||
}
|
||||
|
||||
static BiConsumer<TableItem, Exam> decorateOnExamConsistency(final PageService pageService) {
|
||||
return (item, exam) -> decorateOnExamConsistency(item, exam, pageService);
|
||||
}
|
||||
|
||||
static void decorateOnExamConsistency(
|
||||
final TableItem item,
|
||||
final Exam exam,
|
||||
final PageService pageService) {
|
||||
|
||||
if (exam.getStatus() != ExamStatus.RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
pageService.getRestService().getBuilder(CheckExamConsistency.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
|
||||
.call()
|
||||
.ifPresent(warnings -> {
|
||||
if (warnings != null && !warnings.isEmpty()) {
|
||||
item.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Function<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
|
||||
return exam -> resourceService.getLmsSetupNameFunction()
|
||||
.apply(String.valueOf(exam.lmsSetupId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,285 +1,297 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.gui.content;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
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.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ActivateSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSebRestriction;
|
||||
|
||||
public class ExamSebRestrictionSettings {
|
||||
|
||||
private final static LocTextKey SEB_RESTRICTION_ERROR =
|
||||
new LocTextKey("sebserver.error.exam.seb.restriction");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_TITLE =
|
||||
new LocTextKey("sebserver.exam.action.sebrestriction.details");
|
||||
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_CONFIG_KEYS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.configKeys");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_BROWSER_KEYS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.browserExamKeys");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.WHITELIST_PATHS");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_PERMISSIONS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED");
|
||||
|
||||
static final String PAGE_CONTEXT_ATTR_LMS_TYPE = "ATTR_LMS_TYPE";
|
||||
|
||||
static Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
|
||||
|
||||
return action -> {
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
final ModalInputDialog<FormHandle<?>> dialog =
|
||||
new ModalInputDialog<FormHandle<?>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setDialogWidth(740)
|
||||
.setDialogHeight(400);
|
||||
|
||||
final SebRestrictionPropertiesForm bindFormContext = new SebRestrictionPropertiesForm(
|
||||
pageService,
|
||||
action.pageContext());
|
||||
|
||||
final Predicate<FormHandle<?>> doBind = formHandle -> doCreate(
|
||||
pageService,
|
||||
pageContext,
|
||||
formHandle);
|
||||
|
||||
dialog.open(
|
||||
SEB_RESTRICTION_FORM_TITLE,
|
||||
doBind,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
bindFormContext);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static final boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final FormHandle<?> formHandle) {
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final LmsType lmsType = getLmsType(pageContext);
|
||||
SebRestriction bodyValue = null;
|
||||
try {
|
||||
final Form form = formHandle.getForm();
|
||||
final Collection<String> browserKeys = Utils.getListOfLines(
|
||||
form.getFieldValue(SebRestriction.ATTR_BROWSER_KEYS));
|
||||
|
||||
final Map<String, String> additionalAttributes = new HashMap<>();
|
||||
if (lmsType == LmsType.OPEN_EDX) {
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS));
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS));
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED));
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
|
||||
Utils.convertCarriageReturnToListSeparator(
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)));
|
||||
}
|
||||
|
||||
bodyValue = new SebRestriction(
|
||||
Long.parseLong(entityKey.modelId),
|
||||
null,
|
||||
browserKeys,
|
||||
additionalAttributes);
|
||||
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return !pageService
|
||||
.getRestService()
|
||||
.getBuilder(SaveSebRestriction.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withBody(bodyValue)
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.hasError();
|
||||
}
|
||||
|
||||
private static final class SebRestrictionPropertiesForm
|
||||
implements ModalInputDialogComposer<FormHandle<?>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
|
||||
protected SebRestrictionPropertiesForm(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<?>> compose(final Composite parent) {
|
||||
final RestService restService = this.pageService.getRestService();
|
||||
final ResourceService resourceService = this.pageService.getResourceService();
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final LmsType lmsType = getLmsType(this.pageContext);
|
||||
|
||||
final Composite content = this.pageService
|
||||
.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final SebRestriction sebRestriction = restService
|
||||
.getBuilder(GetSebRestriction.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final PageContext formContext = this.pageContext
|
||||
.copyOf(content)
|
||||
.clearEntityKeys();
|
||||
|
||||
final FormHandle<SebRestriction> formHandle = this.pageService.formBuilder(
|
||||
formContext)
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(false)
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
SebRestriction.ATTR_CONFIG_KEYS,
|
||||
SEB_RESTRICTION_FORM_CONFIG_KEYS,
|
||||
StringUtils.join(sebRestriction.getConfigKeys(), Constants.CARRIAGE_RETURN))
|
||||
.asArea(50)
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
SebRestriction.ATTR_BROWSER_KEYS,
|
||||
SEB_RESTRICTION_FORM_BROWSER_KEYS,
|
||||
StringUtils.join(sebRestriction.getBrowserExamKeys(), Constants.CARRIAGE_RETURN))
|
||||
.asArea())
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.multiCheckboxSelection(
|
||||
OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
|
||||
SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS,
|
||||
sebRestriction.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS),
|
||||
() -> resourceService.sebRestrictionWhiteListResources()))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.multiCheckboxSelection(
|
||||
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
SEB_RESTRICTION_FORM_EDX_PERMISSIONS,
|
||||
sebRestriction.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS),
|
||||
() -> resourceService.sebRestrictionPermissionResources()))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.text(
|
||||
OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
|
||||
SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS,
|
||||
Utils.convertListSeparatorToCarriageReturn(
|
||||
sebRestriction
|
||||
.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)))
|
||||
.asArea())
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.checkbox(
|
||||
OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
|
||||
SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED,
|
||||
sebRestriction
|
||||
.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED)))
|
||||
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
}
|
||||
}
|
||||
|
||||
private static LmsType getLmsType(final PageContext pageContext) {
|
||||
try {
|
||||
return LmsType.valueOf(pageContext.getAttribute(PAGE_CONTEXT_ATTR_LMS_TYPE));
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static PageAction setSebRestriction(
|
||||
final PageAction action,
|
||||
final boolean activateRestriction,
|
||||
final RestService restService) {
|
||||
|
||||
return setSebRestriction(
|
||||
action,
|
||||
activateRestriction,
|
||||
restService,
|
||||
error -> action.pageContext().notifyError(SEB_RESTRICTION_ERROR, error));
|
||||
}
|
||||
|
||||
public static PageAction setSebRestriction(
|
||||
final PageAction action,
|
||||
final boolean activateRestriction,
|
||||
final RestService restService,
|
||||
final Consumer<Exception> errorHandler) {
|
||||
|
||||
restService.getBuilder((activateRestriction)
|
||||
? ActivateSebRestriction.class
|
||||
: DeactivateSebRestriction.class)
|
||||
.withURIVariable(
|
||||
API.PARAM_MODEL_ID,
|
||||
action.getEntityKey().modelId)
|
||||
.call()
|
||||
.onError(errorHandler);
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2020 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.gui.content;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
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.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ActivateSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSebRestriction;
|
||||
|
||||
public class ExamSebRestrictionSettings {
|
||||
|
||||
private final static LocTextKey SEB_RESTRICTION_ERROR =
|
||||
new LocTextKey("sebserver.error.exam.seb.restriction");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_TITLE =
|
||||
new LocTextKey("sebserver.exam.action.sebrestriction.details");
|
||||
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_INFO =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.info");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_INFO_TEXT =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.info-text");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_CONFIG_KEYS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.configKeys");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_BROWSER_KEYS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.browserExamKeys");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.WHITELIST_PATHS");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_PERMISSIONS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED =
|
||||
new LocTextKey("sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED");
|
||||
|
||||
static final String PAGE_CONTEXT_ATTR_LMS_TYPE = "ATTR_LMS_TYPE";
|
||||
|
||||
static Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
|
||||
|
||||
return action -> {
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
final ModalInputDialog<FormHandle<?>> dialog =
|
||||
new ModalInputDialog<FormHandle<?>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setDialogWidth(740)
|
||||
.setDialogHeight(400);
|
||||
|
||||
final SebRestrictionPropertiesForm bindFormContext = new SebRestrictionPropertiesForm(
|
||||
pageService,
|
||||
action.pageContext());
|
||||
|
||||
final Predicate<FormHandle<?>> doBind = formHandle -> doCreate(
|
||||
pageService,
|
||||
pageContext,
|
||||
formHandle);
|
||||
|
||||
dialog.open(
|
||||
SEB_RESTRICTION_FORM_TITLE,
|
||||
doBind,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
bindFormContext);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final FormHandle<?> formHandle) {
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final LmsType lmsType = getLmsType(pageContext);
|
||||
SebRestriction bodyValue = null;
|
||||
try {
|
||||
final Form form = formHandle.getForm();
|
||||
final Collection<String> browserKeys = Utils.getListOfLines(
|
||||
form.getFieldValue(SebRestriction.ATTR_BROWSER_KEYS));
|
||||
|
||||
final Map<String, String> additionalAttributes = new HashMap<>();
|
||||
if (lmsType == LmsType.OPEN_EDX) {
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS));
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS));
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED));
|
||||
additionalAttributes.put(
|
||||
OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
|
||||
Utils.convertCarriageReturnToListSeparator(
|
||||
form.getFieldValue(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)));
|
||||
}
|
||||
|
||||
bodyValue = new SebRestriction(
|
||||
Long.parseLong(entityKey.modelId),
|
||||
null,
|
||||
browserKeys,
|
||||
additionalAttributes);
|
||||
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return !pageService
|
||||
.getRestService()
|
||||
.getBuilder(SaveSebRestriction.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withBody(bodyValue)
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.hasError();
|
||||
}
|
||||
|
||||
private static final class SebRestrictionPropertiesForm
|
||||
implements ModalInputDialogComposer<FormHandle<?>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
|
||||
protected SebRestrictionPropertiesForm(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<?>> compose(final Composite parent) {
|
||||
final RestService restService = this.pageService.getRestService();
|
||||
final ResourceService resourceService = this.pageService.getResourceService();
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final LmsType lmsType = getLmsType(this.pageContext);
|
||||
|
||||
final Composite content = this.pageService
|
||||
.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final SebRestriction sebRestriction = restService
|
||||
.getBuilder(GetSebRestriction.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final PageContext formContext = this.pageContext
|
||||
.copyOf(content)
|
||||
.clearEntityKeys();
|
||||
|
||||
final FormHandle<SebRestriction> formHandle = this.pageService.formBuilder(
|
||||
formContext)
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(false)
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
"Info",
|
||||
SEB_RESTRICTION_FORM_INFO,
|
||||
pageService.getI18nSupport().getText(SEB_RESTRICTION_FORM_INFO_TEXT))
|
||||
.asArea(50)
|
||||
.asHTML()
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
SebRestriction.ATTR_CONFIG_KEYS,
|
||||
SEB_RESTRICTION_FORM_CONFIG_KEYS,
|
||||
StringUtils.join(sebRestriction.getConfigKeys(), Constants.CARRIAGE_RETURN))
|
||||
.asArea(50)
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
SebRestriction.ATTR_BROWSER_KEYS,
|
||||
SEB_RESTRICTION_FORM_BROWSER_KEYS,
|
||||
StringUtils.join(sebRestriction.getBrowserExamKeys(), Constants.CARRIAGE_RETURN))
|
||||
.asArea())
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.multiCheckboxSelection(
|
||||
OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
|
||||
SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS,
|
||||
sebRestriction.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS),
|
||||
resourceService::sebRestrictionWhiteListResources))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.multiCheckboxSelection(
|
||||
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
SEB_RESTRICTION_FORM_EDX_PERMISSIONS,
|
||||
sebRestriction.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS),
|
||||
resourceService::sebRestrictionPermissionResources))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.text(
|
||||
OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
|
||||
SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS,
|
||||
Utils.convertListSeparatorToCarriageReturn(
|
||||
sebRestriction
|
||||
.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)))
|
||||
.asArea())
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.checkbox(
|
||||
OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
|
||||
SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED,
|
||||
sebRestriction
|
||||
.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED)))
|
||||
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
}
|
||||
}
|
||||
|
||||
private static LmsType getLmsType(final PageContext pageContext) {
|
||||
try {
|
||||
return LmsType.valueOf(pageContext.getAttribute(PAGE_CONTEXT_ATTR_LMS_TYPE));
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static PageAction setSebRestriction(
|
||||
final PageAction action,
|
||||
final boolean activateRestriction,
|
||||
final RestService restService) {
|
||||
|
||||
return setSebRestriction(
|
||||
action,
|
||||
activateRestriction,
|
||||
restService,
|
||||
error -> action.pageContext().notifyError(SEB_RESTRICTION_ERROR, error));
|
||||
}
|
||||
|
||||
public static PageAction setSebRestriction(
|
||||
final PageAction action,
|
||||
final boolean activateRestriction,
|
||||
final RestService restService,
|
||||
final Consumer<Exception> errorHandler) {
|
||||
|
||||
restService.getBuilder((activateRestriction)
|
||||
? ActivateSebRestriction.class
|
||||
: DeactivateSebRestriction.class)
|
||||
.withURIVariable(
|
||||
API.PARAM_MODEL_ID,
|
||||
action.getEntityKey().modelId)
|
||||
.call()
|
||||
.onError(errorHandler);
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,275 +1,276 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
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.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
|
||||
|
||||
final class ExamToConfigBindingForm {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamToConfigBindingForm.class);
|
||||
|
||||
private static final LocTextKey NEW_CONFIG_MAPPING_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.title.new");
|
||||
private static final LocTextKey CONFIG_MAPPING_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.title");
|
||||
private static final LocTextKey CONFIG_MAPPING_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.name");
|
||||
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.description");
|
||||
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.status");
|
||||
private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.encryptSecret");
|
||||
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.encryptSecret.confirm");
|
||||
private final static LocTextKey CONFIG_ACTION_NO_CONFIG_MESSAGE =
|
||||
new LocTextKey("sebserver.exam.configuration.action.noconfig.message");
|
||||
|
||||
static Function<PageAction, PageAction> bindFunction(final PageService pageService) {
|
||||
|
||||
return action -> {
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
if (isNew) {
|
||||
final boolean noConfigsAvailable = pageService.getResourceService()
|
||||
.examConfigurationSelectionResources()
|
||||
.isEmpty();
|
||||
|
||||
if (noConfigsAvailable) {
|
||||
throw new PageMessageException(CONFIG_ACTION_NO_CONFIG_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
final ModalInputDialog<FormHandle<ExamConfigurationMap>> dialog =
|
||||
new ModalInputDialog<FormHandle<ExamConfigurationMap>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setLargeDialogWidth();
|
||||
|
||||
final BindFormContext bindFormContext = new BindFormContext(
|
||||
pageService,
|
||||
action.pageContext());
|
||||
|
||||
final Predicate<FormHandle<ExamConfigurationMap>> doBind = formHandle -> doCreate(
|
||||
pageService,
|
||||
pageContext,
|
||||
formHandle);
|
||||
|
||||
// the default page layout
|
||||
final LocTextKey titleKey = (isNew)
|
||||
? NEW_CONFIG_MAPPING_TILE_TEXT_KEY
|
||||
: CONFIG_MAPPING_TILE_TEXT_KEY;
|
||||
|
||||
dialog.open(
|
||||
titleKey,
|
||||
doBind,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
bindFormContext);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static final boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ExamConfigurationMap> formHandle) {
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
final Class<? extends RestCall<ExamConfigurationMap>> restCall = (isNew)
|
||||
? NewExamConfigMapping.class
|
||||
: SaveExamConfigMapping.class;
|
||||
|
||||
return !pageService
|
||||
.getRestService()
|
||||
.getBuilder(restCall)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.map(mapping -> {
|
||||
pageService.executePageAction(
|
||||
pageService.pageActionBuilder(pageContext.clearEntityKeys())
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.withEntityKey(pageContext.getParentEntityKey())
|
||||
.create());
|
||||
return mapping;
|
||||
})
|
||||
.hasError();
|
||||
}
|
||||
|
||||
private static final class BindFormContext implements ModalInputDialogComposer<FormHandle<ExamConfigurationMap>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
|
||||
protected BindFormContext(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<ExamConfigurationMap>> compose(final Composite parent) {
|
||||
|
||||
final Composite grid = this.pageService.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final RestService restService = this.pageService.getRestService();
|
||||
final ResourceService resourceService = this.pageService.getResourceService();
|
||||
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = this.pageContext.getParentEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
final Exam exam = (isNew)
|
||||
? restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow()
|
||||
: null;
|
||||
|
||||
// get data or create new. Handle error if happen
|
||||
final ExamConfigurationMap examConfigurationMap = (isNew)
|
||||
? ExamConfigurationMap.createNew(exam)
|
||||
: restService
|
||||
.getBuilder(GetExamConfigMapping.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> this.pageContext.notifyLoadError(
|
||||
EntityType.EXAM_CONFIGURATION_MAP,
|
||||
error))
|
||||
.getOrThrow();
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = this.pageContext.withEntityKey(examConfigurationMap.getEntityKey());
|
||||
|
||||
final FormHandle<ExamConfigurationMap> formHandle = this.pageService.formBuilder(
|
||||
formContext.copyOf(grid))
|
||||
.readonly(false)
|
||||
.putStaticValueIf(() -> !isNew,
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_ID,
|
||||
examConfigurationMap.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(examConfigurationMap.getInstitutionId()))
|
||||
.putStaticValue(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_EXAM_ID,
|
||||
String.valueOf(examConfigurationMap.examId))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID,
|
||||
CONFIG_MAPPING_NAME_TEXT_KEY,
|
||||
String.valueOf(examConfigurationMap.configurationNodeId),
|
||||
resourceService::examConfigurationSelectionResources)
|
||||
.withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService)))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
FORM_DESCRIPTION_TEXT_KEY,
|
||||
examConfigurationMap.configDescription)
|
||||
.asArea()
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
resourceService.localizedExamConfigStatusName(examConfigurationMap))
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET,
|
||||
FORM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
.addField(FormBuilder.text(
|
||||
ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET,
|
||||
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateFormValuesFromConfigSelection(final Form form, final ResourceService resourceService) {
|
||||
final String configId = form.getFieldValue(Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID);
|
||||
if (StringUtils.isBlank(configId)) {
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
|
||||
} else {
|
||||
try {
|
||||
|
||||
final ConfigurationNode configuration = resourceService
|
||||
.getRestService()
|
||||
.getBuilder(GetExamConfigNode.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, configId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
form.setFieldValue(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
configuration.description);
|
||||
form.setFieldValue(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
resourceService.localizedExamConfigStatusName(configuration));
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to update form values from SEB Configuration selection", e);
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
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.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
|
||||
|
||||
final class ExamToConfigBindingForm {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamToConfigBindingForm.class);
|
||||
|
||||
private static final LocTextKey NEW_CONFIG_MAPPING_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.title.new");
|
||||
private static final LocTextKey CONFIG_MAPPING_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.title");
|
||||
private static final LocTextKey CONFIG_MAPPING_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.name");
|
||||
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.description");
|
||||
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.status");
|
||||
private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.encryptSecret");
|
||||
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.form.encryptSecret.confirm");
|
||||
private final static LocTextKey CONFIG_ACTION_NO_CONFIG_MESSAGE =
|
||||
new LocTextKey("sebserver.exam.configuration.action.noconfig.message");
|
||||
|
||||
static Function<PageAction, PageAction> bindFunction(final PageService pageService) {
|
||||
|
||||
return action -> {
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
if (isNew) {
|
||||
final boolean noConfigsAvailable = pageService.getResourceService()
|
||||
.examConfigurationSelectionResources()
|
||||
.isEmpty();
|
||||
|
||||
if (noConfigsAvailable) {
|
||||
throw new PageMessageException(CONFIG_ACTION_NO_CONFIG_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
final ModalInputDialog<FormHandle<ExamConfigurationMap>> dialog =
|
||||
new ModalInputDialog<FormHandle<ExamConfigurationMap>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setLargeDialogWidth();
|
||||
|
||||
final BindFormContext bindFormContext = new BindFormContext(
|
||||
pageService,
|
||||
action.pageContext());
|
||||
|
||||
final Predicate<FormHandle<ExamConfigurationMap>> doBind = formHandle -> doCreate(
|
||||
pageService,
|
||||
pageContext,
|
||||
formHandle);
|
||||
|
||||
// the default page layout
|
||||
final LocTextKey titleKey = (isNew)
|
||||
? NEW_CONFIG_MAPPING_TILE_TEXT_KEY
|
||||
: CONFIG_MAPPING_TILE_TEXT_KEY;
|
||||
|
||||
dialog.open(
|
||||
titleKey,
|
||||
doBind,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
bindFormContext);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ExamConfigurationMap> formHandle) {
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
final Class<? extends RestCall<ExamConfigurationMap>> restCall = (isNew)
|
||||
? NewExamConfigMapping.class
|
||||
: SaveExamConfigMapping.class;
|
||||
|
||||
return !pageService
|
||||
.getRestService()
|
||||
.getBuilder(restCall)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.map(mapping -> {
|
||||
pageService.executePageAction(
|
||||
pageService.pageActionBuilder(pageContext.clearEntityKeys())
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.withEntityKey(pageContext.getParentEntityKey())
|
||||
.create());
|
||||
return mapping;
|
||||
})
|
||||
.hasError();
|
||||
}
|
||||
|
||||
private static final class BindFormContext implements ModalInputDialogComposer<FormHandle<ExamConfigurationMap>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
|
||||
protected BindFormContext(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<ExamConfigurationMap>> compose(final Composite parent) {
|
||||
|
||||
final Composite grid = this.pageService.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final RestService restService = this.pageService.getRestService();
|
||||
final ResourceService resourceService = this.pageService.getResourceService();
|
||||
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = this.pageContext.getParentEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
final Exam exam = (isNew)
|
||||
? restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow()
|
||||
: null;
|
||||
|
||||
// get data or create new. Handle error if happen
|
||||
final ExamConfigurationMap examConfigurationMap = (isNew)
|
||||
? ExamConfigurationMap.createNew(exam)
|
||||
: restService
|
||||
.getBuilder(GetExamConfigMapping.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> this.pageContext.notifyLoadError(
|
||||
EntityType.EXAM_CONFIGURATION_MAP,
|
||||
error))
|
||||
.getOrThrow();
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = this.pageContext.withEntityKey(examConfigurationMap.getEntityKey());
|
||||
|
||||
final FormHandle<ExamConfigurationMap> formHandle = this.pageService.formBuilder(
|
||||
formContext.copyOf(grid))
|
||||
.readonly(false)
|
||||
.putStaticValueIf(() -> !isNew,
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_ID,
|
||||
examConfigurationMap.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(examConfigurationMap.getInstitutionId()))
|
||||
.putStaticValue(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_EXAM_ID,
|
||||
String.valueOf(examConfigurationMap.examId))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID,
|
||||
CONFIG_MAPPING_NAME_TEXT_KEY,
|
||||
String.valueOf(examConfigurationMap.configurationNodeId),
|
||||
resourceService::examConfigurationSelectionResources)
|
||||
.withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService))
|
||||
.mandatory())
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
FORM_DESCRIPTION_TEXT_KEY,
|
||||
examConfigurationMap.configDescription)
|
||||
.asArea()
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
resourceService.localizedExamConfigStatusName(examConfigurationMap))
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET,
|
||||
FORM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
.addField(FormBuilder.text(
|
||||
ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET,
|
||||
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateFormValuesFromConfigSelection(final Form form, final ResourceService resourceService) {
|
||||
final String configId = form.getFieldValue(Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID);
|
||||
if (StringUtils.isBlank(configId)) {
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
|
||||
} else {
|
||||
try {
|
||||
|
||||
final ConfigurationNode configuration = resourceService
|
||||
.getRestService()
|
||||
.getBuilder(GetExamConfigNode.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, configId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
form.setFieldValue(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
configuration.description);
|
||||
form.setFieldValue(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
resourceService.localizedExamConfigStatusName(configuration));
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to update form values from SEB Configuration selection", e);
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
|
||||
form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,203 +1,206 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
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.LocTextKey;
|
||||
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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import io.micrometer.core.instrument.util.StringUtils;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class IndicatorForm implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey NEW_INDICATOR_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.title.new");
|
||||
private static final LocTextKey INDICATOR_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.title");
|
||||
private static final LocTextKey FORM_THRESHOLDS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.thresholds");
|
||||
private static final LocTextKey FORM_COLOR_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.color");
|
||||
private static final LocTextKey FORM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.type");
|
||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.name");
|
||||
private static final LocTextKey FORM_EXAM_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.exam");
|
||||
private static final LocTextKey FORM_DESC_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.description");
|
||||
|
||||
private static final String INDICATOR_TYPE_DESC_PREFIX =
|
||||
"sebserver.exam.indicator.type.description.";
|
||||
private static final String TYPE_DESCRIPTION_FIELD_NAME =
|
||||
"typeDescription";
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
|
||||
protected IndicatorForm(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.i18nSupport = pageService.getI18nSupport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
final boolean isReadonly = pageContext.isReadonly();
|
||||
|
||||
final Exam exam = restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
|
||||
// get data or create new. Handle error if happen
|
||||
final Indicator indicator = (isNew)
|
||||
? Indicator.createNew(exam)
|
||||
: restService
|
||||
.getBuilder(GetIndicator.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.INDICATOR, error))
|
||||
.getOrThrow();
|
||||
|
||||
final boolean typeSet = indicator.type != null;
|
||||
final String typeDescription = (typeSet)
|
||||
? Utils.formatLineBreaks(this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + indicator.type.name))
|
||||
: Constants.EMPTY_NOTE;
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = pageContext.withEntityKey(indicator.getEntityKey());
|
||||
|
||||
// the default page layout
|
||||
final LocTextKey titleKey = (isNew)
|
||||
? NEW_INDICATOR_TILE_TEXT_KEY
|
||||
: INDICATOR_TILE_TEXT_KEY;
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
formContext.getParent(),
|
||||
titleKey);
|
||||
|
||||
final FormHandle<Indicator> formHandle = this.pageService.formBuilder(
|
||||
formContext.copyOf(content))
|
||||
.readonly(isReadonly)
|
||||
.putStaticValueIf(() -> !isNew,
|
||||
Domain.INDICATOR.ATTR_ID,
|
||||
indicator.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.EXAM.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(exam.getInstitutionId()))
|
||||
.putStaticValue(
|
||||
Domain.INDICATOR.ATTR_EXAM_ID,
|
||||
parentEntityKey.getModelId())
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
FORM_EXAM_TEXT_KEY,
|
||||
exam.name)
|
||||
.readonly(true))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.INDICATOR.ATTR_NAME,
|
||||
FORM_NAME_TEXT_KEY,
|
||||
indicator.name))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.INDICATOR.ATTR_TYPE,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
(indicator.type != null) ? indicator.type.name() : null,
|
||||
this.resourceService::indicatorTypeResources)
|
||||
.withSelectionListener(this::updateForm))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
TYPE_DESCRIPTION_FIELD_NAME,
|
||||
FORM_DESC_TEXT_KEY,
|
||||
typeDescription)
|
||||
.asArea()
|
||||
.readonly(true)
|
||||
.withInputSpan(6))
|
||||
|
||||
.addField(FormBuilder.colorSelection(
|
||||
Domain.INDICATOR.ATTR_COLOR,
|
||||
FORM_COLOR_TEXT_KEY,
|
||||
indicator.defaultColor)
|
||||
.withEmptyCellSeparation(false))
|
||||
.addField(FormBuilder.thresholdList(
|
||||
Domain.THRESHOLD.REFERENCE_NAME,
|
||||
FORM_THRESHOLDS_TEXT_KEY,
|
||||
indicator))
|
||||
|
||||
.buildFor((isNew)
|
||||
? restService.getRestCall(NewIndicator.class)
|
||||
: restService.getRestCall(SaveIndicator.class));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_SAVE)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> !isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_CANCEL_MODIFY)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(this.pageService.backToCurrentFunction())
|
||||
.publishIf(() -> !isReadonly);
|
||||
|
||||
}
|
||||
|
||||
private final void updateForm(final Form form) {
|
||||
final String typeValue = form.getFieldValue(Domain.INDICATOR.ATTR_TYPE);
|
||||
if (StringUtils.isNotBlank(typeValue)) {
|
||||
form.setFieldValue(
|
||||
TYPE_DESCRIPTION_FIELD_NAME,
|
||||
this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + typeValue));
|
||||
} else {
|
||||
form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
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.LocTextKey;
|
||||
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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import io.micrometer.core.instrument.util.StringUtils;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class IndicatorForm implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey NEW_INDICATOR_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.title.new");
|
||||
private static final LocTextKey INDICATOR_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.title");
|
||||
private static final LocTextKey FORM_THRESHOLDS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.thresholds");
|
||||
private static final LocTextKey FORM_COLOR_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.color");
|
||||
private static final LocTextKey FORM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.type");
|
||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.name");
|
||||
private static final LocTextKey FORM_EXAM_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.exam");
|
||||
private static final LocTextKey FORM_DESC_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.form.description");
|
||||
|
||||
private static final String INDICATOR_TYPE_DESC_PREFIX =
|
||||
"sebserver.exam.indicator.type.description.";
|
||||
private static final String TYPE_DESCRIPTION_FIELD_NAME =
|
||||
"typeDescription";
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
|
||||
protected IndicatorForm(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.i18nSupport = pageService.getI18nSupport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
final boolean isReadonly = pageContext.isReadonly();
|
||||
|
||||
final Exam exam = restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
|
||||
// get data or create new. Handle error if happen
|
||||
final Indicator indicator = (isNew)
|
||||
? Indicator.createNew(exam)
|
||||
: restService
|
||||
.getBuilder(GetIndicator.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.INDICATOR, error))
|
||||
.getOrThrow();
|
||||
|
||||
final boolean typeSet = indicator.type != null;
|
||||
final String typeDescription = (typeSet)
|
||||
? Utils.formatLineBreaks(this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + indicator.type.name))
|
||||
: Constants.EMPTY_NOTE;
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = pageContext.withEntityKey(indicator.getEntityKey());
|
||||
|
||||
// the default page layout
|
||||
final LocTextKey titleKey = (isNew)
|
||||
? NEW_INDICATOR_TILE_TEXT_KEY
|
||||
: INDICATOR_TILE_TEXT_KEY;
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
formContext.getParent(),
|
||||
titleKey);
|
||||
|
||||
final FormHandle<Indicator> formHandle = this.pageService.formBuilder(
|
||||
formContext.copyOf(content))
|
||||
.readonly(isReadonly)
|
||||
.putStaticValueIf(() -> !isNew,
|
||||
Domain.INDICATOR.ATTR_ID,
|
||||
indicator.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.EXAM.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(exam.getInstitutionId()))
|
||||
.putStaticValue(
|
||||
Domain.INDICATOR.ATTR_EXAM_ID,
|
||||
parentEntityKey.getModelId())
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
FORM_EXAM_TEXT_KEY,
|
||||
exam.name)
|
||||
.readonly(true))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.INDICATOR.ATTR_NAME,
|
||||
FORM_NAME_TEXT_KEY,
|
||||
indicator.name)
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.INDICATOR.ATTR_TYPE,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
(indicator.type != null) ? indicator.type.name() : null,
|
||||
this.resourceService::indicatorTypeResources)
|
||||
.withSelectionListener(this::updateForm)
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
TYPE_DESCRIPTION_FIELD_NAME,
|
||||
FORM_DESC_TEXT_KEY,
|
||||
typeDescription)
|
||||
.asArea()
|
||||
.asHTML(true)
|
||||
.readonly(true)
|
||||
.withInputSpan(6))
|
||||
|
||||
.addField(FormBuilder.colorSelection(
|
||||
Domain.INDICATOR.ATTR_COLOR,
|
||||
FORM_COLOR_TEXT_KEY,
|
||||
indicator.defaultColor)
|
||||
.withEmptyCellSeparation(false))
|
||||
.addField(FormBuilder.thresholdList(
|
||||
Domain.THRESHOLD.REFERENCE_NAME,
|
||||
FORM_THRESHOLDS_TEXT_KEY,
|
||||
indicator))
|
||||
|
||||
.buildFor((isNew)
|
||||
? restService.getRestCall(NewIndicator.class)
|
||||
: restService.getRestCall(SaveIndicator.class));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_SAVE)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> !isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_CANCEL_MODIFY)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(this.pageService.backToCurrentFunction())
|
||||
.publishIf(() -> !isReadonly);
|
||||
|
||||
}
|
||||
|
||||
private void updateForm(final Form form) {
|
||||
final String typeValue = form.getFieldValue(Domain.INDICATOR.ATTR_TYPE);
|
||||
if (StringUtils.isNotBlank(typeValue)) {
|
||||
form.setFieldValue(
|
||||
TYPE_DESCRIPTION_FIELD_NAME,
|
||||
this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + typeValue));
|
||||
} else {
|
||||
form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,420 +1,428 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.AttributeKeys;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
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.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.ActivateLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.DeactivateLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.NewLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.SaveLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetupAdHoc;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class LmsSetupForm implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.title");
|
||||
private static final LocTextKey NEW_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.title.new");
|
||||
|
||||
private static final LocTextKey FORM_SECRET_LMS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.secret.lms");
|
||||
private static final LocTextKey FORM_CLIENTNAME_LMS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.clientname.lms");
|
||||
private static final LocTextKey FORM_URL_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.url");
|
||||
private static final LocTextKey FORM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.type");
|
||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.name");
|
||||
private static final LocTextKey FORM_INSTITUTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.institution");
|
||||
private static final LocTextKey FORM_PROXY_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy");
|
||||
private static final LocTextKey FORM_PROXY_HOST_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy.host");
|
||||
private static final LocTextKey FORM_PROXY_PORT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy.port");
|
||||
private static final LocTextKey FORM_PROXY_AUTH_CREDENTIALS_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy.auth-credentials");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
|
||||
protected LmsSetupForm(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
|
||||
final UserInfo user = currentUser.get();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean readonly = pageContext.isReadonly();
|
||||
|
||||
final BooleanSupplier isNew = () -> entityKey == null;
|
||||
final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean();
|
||||
final BooleanSupplier isSEBAdmin = () -> user.hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
final BooleanSupplier isEdit = () -> !readonly;
|
||||
|
||||
// get data or create new. handle error if happen
|
||||
final LmsSetup lmsSetup = isNew.getAsBoolean()
|
||||
? LmsSetup.createNew((parentEntityKey != null)
|
||||
? Long.valueOf(parentEntityKey.modelId)
|
||||
: user.institutionId)
|
||||
: restService
|
||||
.getBuilder(GetLmsSetup.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.LMS_SETUP, error))
|
||||
.getOrThrow();
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = pageContext.withEntityKey(lmsSetup.getEntityKey());
|
||||
// the default page layout with title
|
||||
final LocTextKey titleKey = isNotNew.getAsBoolean()
|
||||
? TITLE_TEXT_KEY
|
||||
: NEW_TITLE_TEXT_KEY;
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
formContext.getParent(),
|
||||
titleKey);
|
||||
|
||||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup);
|
||||
final boolean writeGrant = userGrantCheck.w();
|
||||
final boolean modifyGrant = userGrantCheck.m();
|
||||
final boolean institutionActive = restService.getBuilder(GetInstitution.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(lmsSetup.getInstitutionId()))
|
||||
.call()
|
||||
.map(inst -> inst.active)
|
||||
.getOr(false);
|
||||
|
||||
// The LMS Setup form
|
||||
final LmsType lmsType = lmsSetup.getLmsType();
|
||||
final FormHandle<LmsSetup> formHandle = this.pageService.formBuilder(
|
||||
formContext.copyOf(content), 8)
|
||||
.withDefaultSpanLabel(2)
|
||||
.withDefaultSpanInput(5)
|
||||
.withDefaultSpanEmptyCell(1)
|
||||
.readonly(readonly)
|
||||
.putStaticValueIf(isNotNew,
|
||||
Domain.LMS_SETUP.ATTR_ID,
|
||||
lmsSetup.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(lmsSetup.getInstitutionId()))
|
||||
.putStaticValueIf(isNotNew,
|
||||
Domain.LMS_SETUP.ATTR_LMS_TYPE,
|
||||
String.valueOf(lmsSetup.getLmsType()))
|
||||
.addFieldIf(
|
||||
isSEBAdmin,
|
||||
() -> FormBuilder.singleSelection(
|
||||
Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
|
||||
FORM_INSTITUTION_TEXT_KEY,
|
||||
String.valueOf(lmsSetup.getInstitutionId()),
|
||||
() -> this.resourceService.institutionResource())
|
||||
.readonly(true))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_NAME,
|
||||
FORM_NAME_TEXT_KEY,
|
||||
lmsSetup.getName()))
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.LMS_SETUP.ATTR_LMS_TYPE,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
(lmsType != null) ? lmsType.name() : LmsType.MOCKUP.name(),
|
||||
this.resourceService::lmsTypeResources)
|
||||
.readonlyIf(isNotNew))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_URL,
|
||||
FORM_URL_TEXT_KEY,
|
||||
lmsSetup.getLmsApiUrl()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||
FORM_CLIENTNAME_LMS_TEXT_KEY,
|
||||
(lmsSetup.getLmsAuthName() != null) ? lmsSetup.getLmsAuthName() : null))
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||
FORM_SECRET_LMS_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
|
||||
.addFieldIf(
|
||||
() -> readonly,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
|
||||
FORM_PROXY_KEY,
|
||||
(StringUtils.isNotBlank(lmsSetup.getProxyHost()))
|
||||
? lmsSetup.getProxyHost() + Constants.URL_PORT_SEPARATOR + lmsSetup.proxyPort
|
||||
: null))
|
||||
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
|
||||
FORM_PROXY_HOST_KEY,
|
||||
(StringUtils.isNotBlank(lmsSetup.getProxyHost())) ? lmsSetup.getProxyHost() : null)
|
||||
.withInputSpan(3)
|
||||
.withEmptyCellSpan(0))
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_PORT,
|
||||
FORM_PROXY_PORT_KEY,
|
||||
(lmsSetup.getProxyPort() != null) ? String.valueOf(lmsSetup.getProxyPort()) : null)
|
||||
.asNumber(number -> {
|
||||
if (StringUtils.isNotBlank(number)) {
|
||||
Integer.parseInt(number);
|
||||
}
|
||||
})
|
||||
.withInputSpan(1)
|
||||
.withLabelSpan(1)
|
||||
.withEmptyCellSeparation(false)
|
||||
.withEmptyCellSpan(0))
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_USERNAME,
|
||||
FORM_PROXY_AUTH_CREDENTIALS_KEY,
|
||||
(lmsSetup.getProxyAuthUsername() != null) ? lmsSetup.getProxyAuthUsername() : null)
|
||||
.withInputSpan(3)
|
||||
.withEmptyCellSpan(0))
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_SECRET)
|
||||
.asPasswordField()
|
||||
.withInputSpan(2)
|
||||
.withLabelSpan(0)
|
||||
.withEmptyCellSeparation(false)
|
||||
.withEmptyCellSpan(0))
|
||||
|
||||
.buildFor((entityKey == null)
|
||||
? restService.getRestCall(NewLmsSetup.class)
|
||||
: restService.getRestCall(SaveLmsSetup.class));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_NEW)
|
||||
.publishIf(() -> writeGrant && readonly && institutionActive)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && readonly && institutionActive)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_TEST_AND_SAVE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> this.testAdHoc(action, formHandle))
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> modifyGrant && !readonly)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_DEACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withSimpleRestCall(restService, DeactivateLmsSetup.class)
|
||||
.withConfirm(this.pageService.confirmDeactivation(lmsSetup))
|
||||
.publishIf(() -> writeGrant && readonly && institutionActive && lmsSetup.isActive())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_ACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> activate(action, formHandle))
|
||||
.publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_SAVE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> !readonly)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_CANCEL_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.pageService.backToCurrentFunction())
|
||||
.publishIf(() -> !readonly);
|
||||
}
|
||||
|
||||
/** Save and test connection before activation */
|
||||
private PageAction activate(final PageAction action, final FormHandle<LmsSetup> formHandle) {
|
||||
// first test the LMS Setup. If this fails the action execution will stops
|
||||
final PageAction testLmsSetup = this.testLmsSetup(action, formHandle, false);
|
||||
// if LMS Setup test was successful, the activation action applies
|
||||
this.resourceService.getRestService().getBuilder(ActivateLmsSetup.class)
|
||||
.withURIVariable(
|
||||
API.PARAM_MODEL_ID,
|
||||
action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
|
||||
.call()
|
||||
.onError(error -> action.pageContext().notifyActivationError(EntityType.LMS_SETUP, error));
|
||||
|
||||
return testLmsSetup;
|
||||
}
|
||||
|
||||
/** LmsSetup test action implementation */
|
||||
private PageAction testAdHoc(final PageAction action, final FormHandle<LmsSetup> formHandle) {
|
||||
|
||||
// reset previous errors
|
||||
formHandle.process(
|
||||
Utils.truePredicate(),
|
||||
fieldAccessor -> fieldAccessor.resetError());
|
||||
|
||||
// first test the connection on ad hoc object
|
||||
final Result<LmsSetupTestResult> result = this.resourceService.getRestService()
|
||||
.getBuilder(TestLmsSetupAdHoc.class)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call();
|
||||
|
||||
// ... and handle the response
|
||||
if (result.hasError()) {
|
||||
if (formHandle.handleError(result.getError())) {
|
||||
final Exception error = result.getError();
|
||||
if (error instanceof RestCallError) {
|
||||
throw (RestCallError) error;
|
||||
} else {
|
||||
throw new RuntimeException("Cause: ", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handleTestResult(
|
||||
action,
|
||||
a -> {
|
||||
// try to save the LmsSetup
|
||||
final PageAction processFormSave = formHandle.processFormSave(a);
|
||||
processFormSave.pageContext().publishInfo(
|
||||
new LocTextKey("sebserver.lmssetup.action.test.ok"));
|
||||
return processFormSave;
|
||||
},
|
||||
result.getOrThrow());
|
||||
}
|
||||
|
||||
/** LmsSetup test action implementation */
|
||||
private PageAction testLmsSetup(
|
||||
final PageAction action,
|
||||
final FormHandle<LmsSetup> formHandle, final boolean saveFirst) {
|
||||
|
||||
if (saveFirst) {
|
||||
final Result<LmsSetup> postResult = formHandle.doAPIPost();
|
||||
if (postResult.hasError()) {
|
||||
formHandle.handleError(postResult.getError());
|
||||
postResult.getOrThrow();
|
||||
}
|
||||
}
|
||||
|
||||
// Call the testing endpoint with the specified data to test
|
||||
final EntityKey entityKey = action.getEntityKey();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final Result<LmsSetupTestResult> result = restService.getBuilder(TestLmsSetup.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.getModelId())
|
||||
.call();
|
||||
|
||||
// ... and handle the response
|
||||
if (result.hasError()) {
|
||||
if (formHandle.handleError(result.getError())) {
|
||||
throw new PageMessageException(
|
||||
new LocTextKey("sebserver.lmssetup.action.test.missingParameter"));
|
||||
}
|
||||
}
|
||||
|
||||
return handleTestResult(
|
||||
action,
|
||||
a -> {
|
||||
action.pageContext().publishInfo(
|
||||
new LocTextKey("sebserver.lmssetup.action.test.ok"));
|
||||
|
||||
return action;
|
||||
},
|
||||
result.getOrThrow());
|
||||
}
|
||||
|
||||
private PageAction handleTestResult(
|
||||
final PageAction action,
|
||||
final Function<PageAction, PageAction> onOK,
|
||||
final LmsSetupTestResult testResult) {
|
||||
|
||||
if (testResult.isOk()) {
|
||||
return onOK.apply(action);
|
||||
}
|
||||
|
||||
testResult.errors
|
||||
.stream()
|
||||
.findFirst()
|
||||
.ifPresent(error -> {
|
||||
switch (error.errorType) {
|
||||
case TOKEN_REQUEST: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.tokenRequestError",
|
||||
error.message));
|
||||
}
|
||||
case QUIZ_ACCESS_API_REQUEST: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.quizRequestError",
|
||||
error.message));
|
||||
}
|
||||
case QUIZ_RESTRICTION_API_REQUEST: {
|
||||
// NOTE: quiz restriction is not mandatory for functional LmsSetup
|
||||
// so this error is ignored here
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.unknownError",
|
||||
error.message));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return onOK.apply(action);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.AttributeKeys;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
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.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.ActivateLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.DeactivateLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.NewLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.SaveLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetupAdHoc;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class LmsSetupForm implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.title");
|
||||
private static final LocTextKey NEW_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.title.new");
|
||||
|
||||
private static final LocTextKey FORM_SECRET_LMS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.secret.lms");
|
||||
private static final LocTextKey FORM_CLIENTNAME_LMS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.clientname.lms");
|
||||
private static final LocTextKey FORM_URL_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.url");
|
||||
private static final LocTextKey FORM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.type");
|
||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.name");
|
||||
private static final LocTextKey FORM_INSTITUTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.institution");
|
||||
private static final LocTextKey FORM_PROXY_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy");
|
||||
private static final LocTextKey FORM_PROXY_HOST_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy.host");
|
||||
private static final LocTextKey FORM_PROXY_PORT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy.port");
|
||||
private static final LocTextKey FORM_PROXY_AUTH_CREDENTIALS_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.form.proxy.auth-credentials");
|
||||
public static final LocTextKey LMS_SETUP_TEST_OK =
|
||||
new LocTextKey("sebserver.lmssetup.action.test.ok");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
|
||||
protected LmsSetupForm(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
|
||||
final UserInfo user = currentUser.get();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean readonly = pageContext.isReadonly();
|
||||
|
||||
final BooleanSupplier isNew = () -> entityKey == null;
|
||||
final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean();
|
||||
final BooleanSupplier isSEBAdmin = () -> user.hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
final BooleanSupplier isEdit = () -> !readonly;
|
||||
|
||||
// get data or create new. handle error if happen
|
||||
final LmsSetup lmsSetup = isNew.getAsBoolean()
|
||||
? LmsSetup.createNew((parentEntityKey != null)
|
||||
? Long.valueOf(parentEntityKey.modelId)
|
||||
: user.institutionId)
|
||||
: restService
|
||||
.getBuilder(GetLmsSetup.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.LMS_SETUP, error))
|
||||
.getOrThrow();
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = pageContext.withEntityKey(lmsSetup.getEntityKey());
|
||||
// the default page layout with title
|
||||
final LocTextKey titleKey = isNotNew.getAsBoolean()
|
||||
? TITLE_TEXT_KEY
|
||||
: NEW_TITLE_TEXT_KEY;
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
formContext.getParent(),
|
||||
titleKey);
|
||||
|
||||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup);
|
||||
final boolean writeGrant = userGrantCheck.w();
|
||||
final boolean modifyGrant = userGrantCheck.m();
|
||||
final boolean institutionActive = restService.getBuilder(GetInstitution.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(lmsSetup.getInstitutionId()))
|
||||
.call()
|
||||
.map(inst -> inst.active)
|
||||
.getOr(false);
|
||||
|
||||
// The LMS Setup form
|
||||
final LmsType lmsType = lmsSetup.getLmsType();
|
||||
final FormHandle<LmsSetup> formHandle = this.pageService.formBuilder(
|
||||
formContext.copyOf(content), 8)
|
||||
.withDefaultSpanLabel(2)
|
||||
.withDefaultSpanInput(5)
|
||||
.withDefaultSpanEmptyCell(1)
|
||||
.readonly(readonly)
|
||||
.putStaticValueIf(isNotNew,
|
||||
Domain.LMS_SETUP.ATTR_ID,
|
||||
lmsSetup.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(lmsSetup.getInstitutionId()))
|
||||
.putStaticValueIf(isNotNew,
|
||||
Domain.LMS_SETUP.ATTR_LMS_TYPE,
|
||||
String.valueOf(lmsSetup.getLmsType()))
|
||||
|
||||
.addFieldIf(
|
||||
isSEBAdmin,
|
||||
() -> FormBuilder.singleSelection(
|
||||
Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
|
||||
FORM_INSTITUTION_TEXT_KEY,
|
||||
String.valueOf(lmsSetup.getInstitutionId()),
|
||||
this.resourceService::institutionResource)
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_NAME,
|
||||
FORM_NAME_TEXT_KEY,
|
||||
lmsSetup.getName())
|
||||
.mandatory(!readonly))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.LMS_SETUP.ATTR_LMS_TYPE,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
(lmsType != null) ? lmsType.name() : LmsType.MOCKUP.name(),
|
||||
this.resourceService::lmsTypeResources)
|
||||
.readonlyIf(isNotNew)
|
||||
.mandatory(!readonly))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_URL,
|
||||
FORM_URL_TEXT_KEY,
|
||||
lmsSetup.getLmsApiUrl())
|
||||
.mandatory(!readonly))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||
FORM_CLIENTNAME_LMS_TEXT_KEY,
|
||||
lmsSetup.getLmsAuthName())
|
||||
.mandatory(!readonly))
|
||||
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||
FORM_SECRET_LMS_TEXT_KEY)
|
||||
.asPasswordField()
|
||||
.mandatory(!readonly))
|
||||
|
||||
.addFieldIf(
|
||||
() -> readonly,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
|
||||
FORM_PROXY_KEY,
|
||||
(StringUtils.isNotBlank(lmsSetup.getProxyHost()))
|
||||
? lmsSetup.getProxyHost() + Constants.URL_PORT_SEPARATOR + lmsSetup.proxyPort
|
||||
: null))
|
||||
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
|
||||
FORM_PROXY_HOST_KEY,
|
||||
(StringUtils.isNotBlank(lmsSetup.getProxyHost())) ? lmsSetup.getProxyHost() : null)
|
||||
.withInputSpan(3)
|
||||
.withEmptyCellSpan(0))
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_PORT,
|
||||
FORM_PROXY_PORT_KEY,
|
||||
(lmsSetup.getProxyPort() != null) ? String.valueOf(lmsSetup.getProxyPort()) : null)
|
||||
.asNumber(number -> {
|
||||
if (StringUtils.isNotBlank(number)) {
|
||||
Integer.parseInt(number);
|
||||
}
|
||||
})
|
||||
.withInputSpan(1)
|
||||
.withLabelSpan(1)
|
||||
.withEmptyCellSeparation(false)
|
||||
.withEmptyCellSpan(0))
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(
|
||||
Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_USERNAME,
|
||||
FORM_PROXY_AUTH_CREDENTIALS_KEY,
|
||||
lmsSetup.getProxyAuthUsername())
|
||||
.withInputSpan(3)
|
||||
.withEmptyCellSpan(0))
|
||||
.addFieldIf(
|
||||
isEdit,
|
||||
() -> FormBuilder.text(Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_SECRET)
|
||||
.asPasswordField()
|
||||
.withInputSpan(2)
|
||||
.withLabelSpan(0)
|
||||
.withEmptyCellSeparation(false)
|
||||
.withEmptyCellSpan(0))
|
||||
|
||||
.buildFor((entityKey == null)
|
||||
? restService.getRestCall(NewLmsSetup.class)
|
||||
: restService.getRestCall(SaveLmsSetup.class));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_NEW)
|
||||
.publishIf(() -> writeGrant && readonly && institutionActive)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && readonly && institutionActive)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_DEACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withSimpleRestCall(restService, DeactivateLmsSetup.class)
|
||||
.withConfirm(this.pageService.confirmDeactivation(lmsSetup))
|
||||
.publishIf(() -> writeGrant && readonly && institutionActive && lmsSetup.isActive())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_ACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> activate(action, formHandle, restService))
|
||||
.publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_SAVE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> !readonly)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_SAVE_AND_ACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> {
|
||||
this.testAdHoc(action, formHandle);
|
||||
PageAction newAction = formHandle.saveAndActivate(action);
|
||||
pageContext.publishInfo(LMS_SETUP_TEST_OK);
|
||||
return newAction;
|
||||
})
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> !readonly && !lmsSetup.isActive())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_CANCEL_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.pageService.backToCurrentFunction())
|
||||
.publishIf(() -> !readonly);
|
||||
}
|
||||
|
||||
/** Save and test connection before activation */
|
||||
public static PageAction activate(
|
||||
final PageAction action,
|
||||
final FormHandle<LmsSetup> formHandle,
|
||||
final RestService restService) {
|
||||
|
||||
// first test the LMS Setup. If this fails the action execution will stops
|
||||
final PageAction testLmsSetup = testLmsSetup(action, formHandle, restService);
|
||||
// if LMS Setup test was successful, the activation action applies
|
||||
restService.getBuilder(ActivateLmsSetup.class)
|
||||
.withURIVariable(
|
||||
API.PARAM_MODEL_ID,
|
||||
action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
|
||||
.call()
|
||||
.onError(error -> action.pageContext().notifyActivationError(EntityType.LMS_SETUP, error));
|
||||
|
||||
return testLmsSetup;
|
||||
}
|
||||
|
||||
/** LmsSetup test action implementation */
|
||||
private PageAction testAdHoc(final PageAction action, final FormHandle<LmsSetup> formHandle) {
|
||||
|
||||
// reset previous errors
|
||||
formHandle.process(
|
||||
Utils.truePredicate(),
|
||||
Form.FormFieldAccessor::resetError);
|
||||
|
||||
// first test the connection on ad hoc object
|
||||
final Result<LmsSetupTestResult> result = this.resourceService.getRestService()
|
||||
.getBuilder(TestLmsSetupAdHoc.class)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call();
|
||||
|
||||
// ... and handle the response
|
||||
if (result.hasError()) {
|
||||
if (formHandle.handleError(result.getError())) {
|
||||
final Exception error = result.getError();
|
||||
if (error instanceof RestCallError) {
|
||||
throw (RestCallError) error;
|
||||
} else {
|
||||
throw new RuntimeException("Cause: ", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
/** LmsSetup test action implementation */
|
||||
public static PageAction testLmsSetup(
|
||||
final PageAction action,
|
||||
final FormHandle<LmsSetup> formHandle,
|
||||
final RestService restService) {
|
||||
|
||||
// Call the testing endpoint with the specified data to test
|
||||
final EntityKey entityKey = action.getEntityKey();
|
||||
final Result<LmsSetupTestResult> result = restService.getBuilder(TestLmsSetup.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.getModelId())
|
||||
.call();
|
||||
|
||||
// ... and handle the response
|
||||
if (result.hasError()) {
|
||||
if (formHandle != null && formHandle.handleError(result.getError())) {
|
||||
throw new PageMessageException(
|
||||
new LocTextKey("sebserver.lmssetup.action.test.missingParameter"));
|
||||
}
|
||||
|
||||
result.getOrThrow();
|
||||
}
|
||||
|
||||
return handleTestResult(
|
||||
action,
|
||||
a -> {
|
||||
action.pageContext().publishInfo(
|
||||
new LocTextKey("sebserver.lmssetup.action.test.ok"));
|
||||
|
||||
return action;
|
||||
},
|
||||
result.getOrThrow());
|
||||
}
|
||||
|
||||
private static PageAction handleTestResult(
|
||||
final PageAction action,
|
||||
final Function<PageAction, PageAction> onOK,
|
||||
final LmsSetupTestResult testResult) {
|
||||
|
||||
if (testResult.isOk()) {
|
||||
return onOK.apply(action);
|
||||
}
|
||||
|
||||
testResult.errors
|
||||
.stream()
|
||||
.findFirst()
|
||||
.ifPresent(error -> {
|
||||
switch (error.errorType) {
|
||||
case TOKEN_REQUEST: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.tokenRequestError",
|
||||
error.message));
|
||||
}
|
||||
case QUIZ_ACCESS_API_REQUEST: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.quizRequestError",
|
||||
error.message));
|
||||
}
|
||||
case QUIZ_RESTRICTION_API_REQUEST: {
|
||||
// NOTE: quiz restriction is not mandatory for functional LmsSetup
|
||||
// so this error is ignored here
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new PageMessageException(new LocTextKey(
|
||||
"sebserver.lmssetup.action.test.unknownError",
|
||||
error.message));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return onOK.apply(action);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,185 +1,204 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class LmsSetupList implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
|
||||
new LocTextKey("sebserver.lmssetup.list.action.no.modify.privilege");
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.info.pleaseSelect");
|
||||
private static final LocTextKey ACTIVITY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.active");
|
||||
private static final LocTextKey TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.type");
|
||||
private static final LocTextKey NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.name");
|
||||
private static final LocTextKey INSTITUTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.institution");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.empty");
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.title");
|
||||
|
||||
private final TableFilterAttribute institutionFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
private final TableFilterAttribute activityFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
protected LmsSetupList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.institutionFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Entity.FILTER_ATTR_INSTITUTION,
|
||||
this.resourceService::institutionResource);
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
LmsSetup.FILTER_ATTR_LMS_TYPE,
|
||||
this.resourceService::lmsTypeResources);
|
||||
|
||||
this.activityFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
UserInfo.FILTER_ATTR_ACTIVE,
|
||||
this.resourceService::activityResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
TITLE_TEXT_KEY);
|
||||
|
||||
final boolean isSEBAdmin = currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
// table
|
||||
final EntityTable<LmsSetup> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetLmsSetupPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withColumnIf(
|
||||
() -> isSEBAdmin,
|
||||
() -> new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
|
||||
INSTITUTION_TEXT_KEY,
|
||||
lmsSetupInstitutionNameFunction(this.resourceService))
|
||||
.withFilter(this.institutionFilter))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_NAME,
|
||||
NAME_TEXT_KEY,
|
||||
LmsSetup::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_LMS_TYPE,
|
||||
TYPE_TEXT_KEY,
|
||||
this::lmsSetupTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.localized()
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_ACTIVE,
|
||||
ACTIVITY_TEXT_KEY,
|
||||
this.pageService.getResourceService().<LmsSetup> localizedActivityFunction())
|
||||
.withFilter(this.activityFilter)
|
||||
.sortable())
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
|
||||
.create())
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.LMS_SETUP);
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_NEW)
|
||||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> table.hasAnyContent())
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST)
|
||||
.withSelect(
|
||||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
|
||||
PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && table.hasAnyContent());
|
||||
|
||||
}
|
||||
|
||||
private String lmsSetupTypeName(final LmsSetup lmsSetup) {
|
||||
if (lmsSetup.lmsType == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.resourceService.getI18nSupport()
|
||||
.getText(ResourceService.LMSSETUP_TYPE_PREFIX + lmsSetup.lmsType.name());
|
||||
}
|
||||
|
||||
private static Function<LmsSetup, String> lmsSetupInstitutionNameFunction(final ResourceService resourceService) {
|
||||
return lmsSetup -> resourceService.getInstitutionNameFunction()
|
||||
.apply(String.valueOf(lmsSetup.institutionId));
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class LmsSetupList implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
|
||||
new LocTextKey("sebserver.lmssetup.list.action.no.modify.privilege");
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.info.pleaseSelect");
|
||||
private static final LocTextKey ACTIVITY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.active");
|
||||
private static final LocTextKey TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.type");
|
||||
private static final LocTextKey NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.name");
|
||||
private static final LocTextKey INSTITUTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.column.institution");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.empty");
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.lmssetup.list.title");
|
||||
|
||||
private final TableFilterAttribute institutionFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
private final TableFilterAttribute activityFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
protected LmsSetupList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.institutionFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Entity.FILTER_ATTR_INSTITUTION,
|
||||
this.resourceService::institutionResource);
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
LmsSetup.FILTER_ATTR_LMS_TYPE,
|
||||
this.resourceService::lmsTypeResources);
|
||||
|
||||
this.activityFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
UserInfo.FILTER_ATTR_ACTIVE,
|
||||
this.resourceService::activityResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
TITLE_TEXT_KEY);
|
||||
|
||||
final boolean isSEBAdmin = currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
// table
|
||||
final EntityTable<LmsSetup> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetLmsSetupPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withColumnIf(
|
||||
() -> isSEBAdmin,
|
||||
() -> new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
|
||||
INSTITUTION_TEXT_KEY,
|
||||
lmsSetupInstitutionNameFunction(this.resourceService))
|
||||
.withFilter(this.institutionFilter))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_NAME,
|
||||
NAME_TEXT_KEY,
|
||||
LmsSetup::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_LMS_TYPE,
|
||||
TYPE_TEXT_KEY,
|
||||
this::lmsSetupTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.localized()
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.LMS_SETUP.ATTR_ACTIVE,
|
||||
ACTIVITY_TEXT_KEY,
|
||||
this.pageService.getResourceService().<LmsSetup> localizedActivityFunction())
|
||||
.withFilter(this.activityFilter)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
ActionDefinition.LMS_SETUP_TOGGLE_ACTIVITY,
|
||||
ActionDefinition.LMS_SETUP_ACTIVATE,
|
||||
ActionDefinition.LMS_SETUP_DEACTIVATE,
|
||||
pageContext,
|
||||
ActionDefinition.LMS_SETUP_VIEW_FROM_LIST,
|
||||
ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST,
|
||||
ActionDefinition.LMS_SETUP_TOGGLE_ACTIVITY))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.LMS_SETUP);
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_NEW)
|
||||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(table::hasAnyContent, false)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST)
|
||||
.withSelect(
|
||||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
|
||||
PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && table.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_TOGGLE_ACTIVITY)
|
||||
.withExec(this.pageService.activationToggleActionFunction(
|
||||
table,
|
||||
EMPTY_SELECTION_TEXT_KEY,
|
||||
action -> LmsSetupForm.testLmsSetup(action, null, restService)))
|
||||
.withConfirm(this.pageService.confirmDeactivation(table))
|
||||
.publishIf(() -> userGrant.im() && table.hasAnyContent(), false);
|
||||
|
||||
}
|
||||
|
||||
private String lmsSetupTypeName(final LmsSetup lmsSetup) {
|
||||
if (lmsSetup.lmsType == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.resourceService.getI18nSupport()
|
||||
.getText(ResourceService.LMSSETUP_TYPE_PREFIX + lmsSetup.lmsType.name());
|
||||
}
|
||||
|
||||
private static Function<LmsSetup, String> lmsSetupInstitutionNameFunction(final ResourceService resourceService) {
|
||||
return lmsSetup -> resourceService.getInstitutionNameFunction()
|
||||
.apply(String.valueOf(lmsSetup.institutionId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,258 +1,277 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
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.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.LocTextKey;
|
||||
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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientEventPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class MonitoringClientConnection implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.title");
|
||||
|
||||
private static final LocTextKey EVENT_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.empty");
|
||||
private static final LocTextKey LIST_COLUMN_TYPE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.type");
|
||||
private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.clienttime");
|
||||
private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.servertime");
|
||||
private static final LocTextKey LIST_COLUMN_VALUE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.value");
|
||||
private static final LocTextKey LIST_COLUMN_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
|
||||
private static final LocTextKey CONFIRM_QUIT =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
|
||||
|
||||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final long pollInterval;
|
||||
private final int pageSize;
|
||||
|
||||
private final TableFilterAttribute typeFilter;
|
||||
private final TableFilterAttribute textFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, ClientEvent.FILTER_ATTR_TEXT);
|
||||
|
||||
protected MonitoringClientConnection(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
final InstructionProcessor instructionProcessor,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.i18nSupport = resourceService.getI18nSupport();
|
||||
this.instructionProcessor = instructionProcessor;
|
||||
this.pollInterval = pollInterval;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Domain.CLIENT_EVENT.ATTR_TYPE,
|
||||
this.resourceService::clientEventTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final String connectionToken = pageContext.getAttribute(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN);
|
||||
|
||||
if (connectionToken == null) {
|
||||
pageContext.notifyUnexpectedError(new IllegalAccessException("connectionToken has null reference"));
|
||||
return;
|
||||
}
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
PAGE_TITLE_KEY);
|
||||
|
||||
final Exam exam = restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Indicator> indicators = restService.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final RestCall<ClientConnectionData>.RestCallBuilder getConnectionData =
|
||||
restService.getBuilder(GetClientConnectionData.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
|
||||
.withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||
|
||||
final ClientConnectionData connectionData = getConnectionData
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final ClientConnectionDetails clientConnectionDetails = new ClientConnectionDetails(
|
||||
this.pageService,
|
||||
pageContext.copyOf(content),
|
||||
exam,
|
||||
getConnectionData,
|
||||
indicators);
|
||||
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(content, Utils.truePredicate()),
|
||||
this.pollInterval,
|
||||
clientConnectionDetails::updateData,
|
||||
clientConnectionDetails::updateGUI);
|
||||
|
||||
widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
EVENT_LIST_TITLE_KEY);
|
||||
|
||||
// client event table for this connection
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetClientEventPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam(
|
||||
ClientEvent.FILTER_ATTR_CONECTION_ID,
|
||||
entityKey.modelId))
|
||||
.withColumn(new ColumnDefinition<ClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_TYPE,
|
||||
LIST_COLUMN_TYPE_KEY,
|
||||
this.resourceService::getEventTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_EVENT.ATTR_TEXT,
|
||||
LIST_COLUMN_TEXT_KEY,
|
||||
ClientEvent::getText)
|
||||
.withFilter(this.textFilter)
|
||||
.sortable()
|
||||
.withCellTooltip()
|
||||
.widthProportion(4))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
|
||||
LIST_COLUMN_VALUE_KEY,
|
||||
ClientEvent::getValue)
|
||||
.widthProportion(1))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
|
||||
new LocTextKey(LIST_COLUMN_CLIENT_TIME_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getClientTime)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||
new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getServerTime)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
this.pageService
|
||||
.pageActionBuilder(
|
||||
pageContext
|
||||
.clearAttributes()
|
||||
.clearEntityKeys())
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_FROM_DETAILS)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER))
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_QUIT)
|
||||
.withConfirm(() -> CONFIRM_QUIT)
|
||||
.withExec(action -> {
|
||||
this.instructionProcessor.propagateSebQuitInstruction(
|
||||
exam.id,
|
||||
connectionToken,
|
||||
pageContext);
|
||||
return action;
|
||||
})
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) &&
|
||||
connectionData.clientConnection.status == ConnectionStatus.ACTIVE);
|
||||
|
||||
}
|
||||
|
||||
private final String getClientTime(final ClientEvent event) {
|
||||
if (event == null || event.getClientTime() == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getClientTime()));
|
||||
}
|
||||
|
||||
private final String getServerTime(final ClientEvent event) {
|
||||
if (event == null || event.getServerTime() == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getServerTime()));
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
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.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.LocTextKey;
|
||||
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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientEventPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class MonitoringClientConnection implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.title");
|
||||
|
||||
private static final LocTextKey EVENT_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.empty");
|
||||
private static final LocTextKey LIST_COLUMN_TYPE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.type");
|
||||
private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.clienttime");
|
||||
private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.servertime");
|
||||
private static final LocTextKey LIST_COLUMN_VALUE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.value");
|
||||
private static final LocTextKey LIST_COLUMN_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
|
||||
private static final LocTextKey CONFIRM_QUIT =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
|
||||
|
||||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final SebClientLogDetailsPopup sebClientLogDetailsPopup;
|
||||
private final long pollInterval;
|
||||
private final int pageSize;
|
||||
|
||||
private final TableFilterAttribute typeFilter;
|
||||
private final TableFilterAttribute textFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, ClientEvent.FILTER_ATTR_TEXT);
|
||||
|
||||
protected MonitoringClientConnection(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
final InstructionProcessor instructionProcessor,
|
||||
final SebClientLogDetailsPopup sebClientLogDetailsPopup,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.i18nSupport = resourceService.getI18nSupport();
|
||||
this.instructionProcessor = instructionProcessor;
|
||||
this.pollInterval = pollInterval;
|
||||
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Domain.CLIENT_EVENT.ATTR_TYPE,
|
||||
this.resourceService::clientEventTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final String connectionToken = pageContext.getAttribute(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN);
|
||||
|
||||
if (connectionToken == null) {
|
||||
pageContext.notifyUnexpectedError(new IllegalAccessException("connectionToken has null reference"));
|
||||
return;
|
||||
}
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
PAGE_TITLE_KEY);
|
||||
|
||||
final Exam exam = restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Indicator> indicators = restService.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final RestCall<ClientConnectionData>.RestCallBuilder getConnectionData =
|
||||
restService.getBuilder(GetClientConnectionData.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
|
||||
.withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||
|
||||
final ClientConnectionData connectionData = getConnectionData
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final ClientConnectionDetails clientConnectionDetails = new ClientConnectionDetails(
|
||||
this.pageService,
|
||||
pageContext.copyOf(content),
|
||||
exam,
|
||||
getConnectionData,
|
||||
indicators);
|
||||
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(content, Utils.truePredicate()),
|
||||
this.pollInterval,
|
||||
context1 -> clientConnectionDetails.updateData(),
|
||||
context -> clientConnectionDetails.updateGUI());
|
||||
|
||||
widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
EVENT_LIST_TITLE_KEY);
|
||||
|
||||
PageService.PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(
|
||||
pageContext
|
||||
.clearAttributes()
|
||||
.clearEntityKeys());
|
||||
|
||||
|
||||
// client event table for this connection
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetExtendedClientEventPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam(
|
||||
ClientEvent.FILTER_ATTR_CONECTION_ID,
|
||||
entityKey.modelId))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_TYPE,
|
||||
LIST_COLUMN_TYPE_KEY,
|
||||
this.resourceService::getEventTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_TEXT,
|
||||
LIST_COLUMN_TEXT_KEY,
|
||||
ClientEvent::getText)
|
||||
.withFilter(this.textFilter)
|
||||
.sortable()
|
||||
.withCellTooltip()
|
||||
.widthProportion(4))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
|
||||
LIST_COLUMN_VALUE_KEY,
|
||||
ClientEvent::getValue)
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
|
||||
new LocTextKey(LIST_COLUMN_CLIENT_TIME_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getClientTime)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||
new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getServerTime)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
|
||||
.withExec(action -> sebClientLogDetailsPopup.showDetails(action, t.getSingleSelectedROWData()))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_BACK_TO_OVERVIEW)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER))
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_QUIT)
|
||||
.withConfirm(() -> CONFIRM_QUIT)
|
||||
.withExec(action -> {
|
||||
this.instructionProcessor.propagateSebQuitInstruction(
|
||||
exam.id,
|
||||
connectionToken,
|
||||
pageContext);
|
||||
return action;
|
||||
})
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) &&
|
||||
connectionData.clientConnection.status == ConnectionStatus.ACTIVE);
|
||||
|
||||
}
|
||||
|
||||
private String getClientTime(final ClientEvent event) {
|
||||
if (event == null || event.getClientTime() == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getClientTime()));
|
||||
}
|
||||
|
||||
private String getServerTime(final ClientEvent event) {
|
||||
if (event == null || event.getServerTime() == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getServerTime()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,355 +1,361 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
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.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.PageMessageException;
|
||||
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.TemplateComposer;
|
||||
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.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionDataList;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class MonitoringRunningExam implements TemplateComposer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
|
||||
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
|
||||
private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection.active");
|
||||
private static final LocTextKey CONFIRM_QUIT_SELECTED =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm");
|
||||
private static final LocTextKey CONFIRM_QUIT_ALL =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm");
|
||||
private static final LocTextKey CONFIRM_DISABLE_SELECTED =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm");
|
||||
|
||||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final long pollInterval;
|
||||
|
||||
protected MonitoringRunningExam(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
final InstructionProcessor instructionProcessor,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval) {
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.instructionProcessor = instructionProcessor;
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final Exam exam = restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Indicator> indicators = restService.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
new LocTextKey("sebserver.monitoring.exam", exam.name));
|
||||
|
||||
final Composite tablePane = new Composite(content, SWT.NONE);
|
||||
tablePane.setLayout(new GridLayout());
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
gridData.heightHint = 100;
|
||||
tablePane.setLayoutData(gridData);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCall =
|
||||
restService.getBuilder(GetClientConnectionDataList.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId());
|
||||
|
||||
final ClientConnectionTable clientTable = new ClientConnectionTable(
|
||||
this.pageService,
|
||||
tablePane,
|
||||
exam,
|
||||
indicators,
|
||||
restCall);
|
||||
|
||||
clientTable.withDefaultAction(
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(entityKey)
|
||||
.create(),
|
||||
this.pageService);
|
||||
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(content, Utils.truePredicate()),
|
||||
this.pollInterval,
|
||||
context -> clientTable.updateValues(),
|
||||
updateTableGUI(clientTable));
|
||||
|
||||
final BooleanSupplier privilege = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withExec(pageAction -> {
|
||||
final Tuple<String> singleSelection = clientTable.getSingleSelection();
|
||||
if (singleSelection == null) {
|
||||
throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
|
||||
}
|
||||
|
||||
final PageAction copyOfPageAction = PageAction.copyOf(pageAction);
|
||||
copyOfPageAction.withEntityKey(new EntityKey(
|
||||
singleSelection._1,
|
||||
EntityType.CLIENT_CONNECTION));
|
||||
copyOfPageAction.withAttribute(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
||||
singleSelection._2);
|
||||
|
||||
return copyOfPageAction;
|
||||
})
|
||||
.publishIf(privilege)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_ALL)
|
||||
.withExec(action -> this.quitSebClients(action, clientTable, true))
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_SELECTED)
|
||||
.withSelect(
|
||||
() -> this.selectionForQuitInstruction(clientTable),
|
||||
action -> this.quitSebClients(action, clientTable, false),
|
||||
EMPTY_ACTIVE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_DISABLE_SELECTED)
|
||||
.withSelect(
|
||||
clientTable::getSelection,
|
||||
action -> this.disableSebClients(action, clientTable, false),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege);
|
||||
|
||||
if (privilege.getAsBoolean()) {
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final Function<PageAction, PageAction> showStateViewAction(
|
||||
final ClientConnectionTable clientTable,
|
||||
final ConnectionStatus status) {
|
||||
|
||||
return action -> {
|
||||
clientTable.showStatus(status);
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static final Function<PageAction, PageAction> hideStateViewAction(
|
||||
final ClientConnectionTable clientTable,
|
||||
final ConnectionStatus status) {
|
||||
|
||||
return action -> {
|
||||
clientTable.hideStatus(status);
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private Set<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) {
|
||||
final Set<String> connectionTokens = clientTable.getConnectionTokens(
|
||||
ClientConnection.getStatusPredicate(ConnectionStatus.ACTIVE),
|
||||
true);
|
||||
if (connectionTokens == null || connectionTokens.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
return clientTable.getSelection();
|
||||
}
|
||||
|
||||
private PageAction quitSebClients(
|
||||
final PageAction action,
|
||||
final ClientConnectionTable clientTable,
|
||||
final boolean all) {
|
||||
|
||||
this.instructionProcessor.propagateSebQuitInstruction(
|
||||
clientTable.getExam().id,
|
||||
statesPredicate -> clientTable.getConnectionTokens(
|
||||
statesPredicate,
|
||||
!all),
|
||||
action.pageContext());
|
||||
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction disableSebClients(
|
||||
final PageAction action,
|
||||
final ClientConnectionTable clientTable,
|
||||
final boolean all) {
|
||||
|
||||
this.instructionProcessor.disableConnection(
|
||||
clientTable.getExam().id,
|
||||
statesPredicate -> clientTable.getConnectionTokens(
|
||||
statesPredicate,
|
||||
!all),
|
||||
action.pageContext());
|
||||
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
}
|
||||
|
||||
private final Consumer<ServerPushContext> updateTableGUI(final ClientConnectionTable clientTable) {
|
||||
return context -> {
|
||||
if (!context.isDisposed()) {
|
||||
try {
|
||||
clientTable.updateGUI();
|
||||
context.layout();
|
||||
} catch (final Exception e) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Unexpected error while trying to update GUI: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
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.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.PageMessageException;
|
||||
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.TemplateComposer;
|
||||
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.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionDataList;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class MonitoringRunningExam implements TemplateComposer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
|
||||
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
|
||||
private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection.active");
|
||||
private static final LocTextKey CONFIRM_QUIT_SELECTED =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm");
|
||||
private static final LocTextKey CONFIRM_QUIT_ALL =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm");
|
||||
private static final LocTextKey CONFIRM_DISABLE_SELECTED =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm");
|
||||
|
||||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final long pollInterval;
|
||||
|
||||
protected MonitoringRunningExam(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
final InstructionProcessor instructionProcessor,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval) {
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.instructionProcessor = instructionProcessor;
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final Exam exam = restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Indicator> indicators = restService.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
new LocTextKey("sebserver.monitoring.exam", exam.name));
|
||||
|
||||
final Composite tablePane = new Composite(content, SWT.NONE);
|
||||
tablePane.setLayout(new GridLayout());
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
gridData.heightHint = 100;
|
||||
tablePane.setLayoutData(gridData);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCall =
|
||||
restService.getBuilder(GetClientConnectionDataList.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId());
|
||||
|
||||
final ClientConnectionTable clientTable = new ClientConnectionTable(
|
||||
this.pageService,
|
||||
tablePane,
|
||||
exam,
|
||||
indicators,
|
||||
restCall);
|
||||
|
||||
clientTable
|
||||
.withDefaultAction(
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(entityKey)
|
||||
.create(),
|
||||
this.pageService)
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION,
|
||||
ActionDefinition.MONITOR_EXAM_QUIT_SELECTED,
|
||||
ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION));
|
||||
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(content, Utils.truePredicate()),
|
||||
this.pollInterval,
|
||||
context -> clientTable.updateValues(),
|
||||
updateTableGUI(clientTable));
|
||||
|
||||
final BooleanSupplier privilege = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withExec(pageAction -> {
|
||||
final Tuple<String> singleSelection = clientTable.getSingleSelection();
|
||||
if (singleSelection == null) {
|
||||
throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
|
||||
}
|
||||
|
||||
final PageAction copyOfPageAction = PageAction.copyOf(pageAction);
|
||||
copyOfPageAction.withEntityKey(new EntityKey(
|
||||
singleSelection._1,
|
||||
EntityType.CLIENT_CONNECTION));
|
||||
copyOfPageAction.withAttribute(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
||||
singleSelection._2);
|
||||
|
||||
return copyOfPageAction;
|
||||
})
|
||||
.publishIf(privilege, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_ALL)
|
||||
.withExec(action -> this.quitSebClients(action, clientTable, true))
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_SELECTED)
|
||||
.withSelect(
|
||||
() -> this.selectionForQuitInstruction(clientTable),
|
||||
action -> this.quitSebClients(action, clientTable, false),
|
||||
EMPTY_ACTIVE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_DISABLE_SELECTED)
|
||||
.withSelect(
|
||||
clientTable::getSelection,
|
||||
action -> this.disableSebClients(action, clientTable, false),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege, false);
|
||||
|
||||
if (privilege.getAsBoolean()) {
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static Function<PageAction, PageAction> showStateViewAction(
|
||||
final ClientConnectionTable clientTable,
|
||||
final ConnectionStatus status) {
|
||||
|
||||
return action -> {
|
||||
clientTable.showStatus(status);
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static Function<PageAction, PageAction> hideStateViewAction(
|
||||
final ClientConnectionTable clientTable,
|
||||
final ConnectionStatus status) {
|
||||
|
||||
return action -> {
|
||||
clientTable.hideStatus(status);
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private Set<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) {
|
||||
final Set<String> connectionTokens = clientTable.getConnectionTokens(
|
||||
ClientConnection.getStatusPredicate(ConnectionStatus.ACTIVE),
|
||||
true);
|
||||
if (connectionTokens == null || connectionTokens.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
return clientTable.getSelection();
|
||||
}
|
||||
|
||||
private PageAction quitSebClients(
|
||||
final PageAction action,
|
||||
final ClientConnectionTable clientTable,
|
||||
final boolean all) {
|
||||
|
||||
this.instructionProcessor.propagateSebQuitInstruction(
|
||||
clientTable.getExam().id,
|
||||
statesPredicate -> clientTable.getConnectionTokens(
|
||||
statesPredicate,
|
||||
!all),
|
||||
action.pageContext());
|
||||
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction disableSebClients(
|
||||
final PageAction action,
|
||||
final ClientConnectionTable clientTable,
|
||||
final boolean all) {
|
||||
|
||||
this.instructionProcessor.disableConnection(
|
||||
clientTable.getExam().id,
|
||||
statesPredicate -> clientTable.getConnectionTokens(
|
||||
statesPredicate,
|
||||
!all),
|
||||
action.pageContext());
|
||||
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
}
|
||||
|
||||
private Consumer<ServerPushContext> updateTableGUI(final ClientConnectionTable clientTable) {
|
||||
return context -> {
|
||||
if (!context.isDisposed()) {
|
||||
try {
|
||||
clientTable.updateGUI();
|
||||
context.layout();
|
||||
} catch (final Exception e) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Unexpected error while trying to update GUI: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,144 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.LocTextKey;
|
||||
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.PageActionBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetRunningExamPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class MonitoringRunningExamList implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.title");
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.info.pleaseSelect");
|
||||
private final static LocTextKey COLUMN_TITLE_NAME_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.column.name");
|
||||
private final static LocTextKey COLUMN_TITLE_TYPE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.column.type");
|
||||
private final static LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.empty");
|
||||
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
protected MonitoringRunningExamList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_TYPE,
|
||||
this.resourceService::examTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
PAGE_TITLE_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
// table
|
||||
final EntityTable<Exam> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetRunningExamPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRowDecorator(ExamList.decorateOnExamConsistency(this.pageService))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
COLUMN_TITLE_TYPE_KEY,
|
||||
this.resourceService::localizedExamTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
new LocTextKey(
|
||||
"sebserver.monitoring.exam.list.column.startTime",
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getStartTime)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
new LocTextKey(
|
||||
"sebserver.monitoring.exam.list.column.endTime",
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getEndTime)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
|
||||
.create())
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.LocTextKey;
|
||||
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.PageActionBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetRunningExamPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class MonitoringRunningExamList implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.title");
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.info.pleaseSelect");
|
||||
private final static LocTextKey COLUMN_TITLE_NAME_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.column.name");
|
||||
private final static LocTextKey COLUMN_TITLE_TYPE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.column.type");
|
||||
private final static LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.list.empty");
|
||||
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
protected MonitoringRunningExamList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_TYPE,
|
||||
this.resourceService::examTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
PAGE_TITLE_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
// table
|
||||
final EntityTable<Exam> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetRunningExamPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRowDecorator(ExamList.decorateOnExamConsistency(this.pageService))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
COLUMN_TITLE_TYPE_KEY,
|
||||
this.resourceService::localizedExamTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
new LocTextKey(
|
||||
"sebserver.monitoring.exam.list.column.startTime",
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getStartTime)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
new LocTextKey(
|
||||
"sebserver.monitoring.exam.list.column.endTime",
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getEndTime)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.MONITOR_EXAM_FROM_LIST))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,367 +1,373 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
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.TemplateComposer;
|
||||
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.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class QuizDiscoveryList implements TemplateComposer {
|
||||
|
||||
// localized text keys
|
||||
private static final LocTextKey QUIZ_DETAILS_URL_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.url");
|
||||
private static final LocTextKey QUIZ_DETAILS_DESCRIPTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.description");
|
||||
private static final LocTextKey QUIZ_DETAILS_START_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.starttime");
|
||||
private static final LocTextKey QUIZ_DETAILS_END_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.endtime");
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.title");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.empty");
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT =
|
||||
new LocTextKey("sebserver.quizdiscovery.info.pleaseSelect");
|
||||
private final static LocTextKey INSTITUION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.institution");
|
||||
private final static LocTextKey LMS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.lmssetup");
|
||||
private final static LocTextKey NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.name");
|
||||
private final static LocTextKey START_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.starttime");
|
||||
private final static LocTextKey END_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.endtime");
|
||||
private final static LocTextKey DETAILS_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.title");
|
||||
private final static LocTextKey NO_IMPORT_OF_OUT_DATED_QUIZ =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
|
||||
|
||||
private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX =
|
||||
"sebserver.quizdiscovery.quiz.details.additional.";
|
||||
|
||||
// filter attribute models
|
||||
private final TableFilterAttribute institutionFilter;
|
||||
private final TableFilterAttribute lmsFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
|
||||
// dependencies
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final ResourceService resourceService;
|
||||
private final PageService pageService;
|
||||
private final int pageSize;
|
||||
|
||||
protected QuizDiscoveryList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.institutionFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Entity.FILTER_ATTR_INSTITUTION,
|
||||
this.resourceService::institutionResource);
|
||||
|
||||
this.lmsFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
LmsSetup.FILTER_ATTR_LMS_SETUP,
|
||||
this.resourceService::lmsSetupResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = this.widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
TITLE_TEXT_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder =
|
||||
this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final BooleanSupplier isSebAdmin =
|
||||
() -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
|
||||
final Function<String, String> institutionNameFunction =
|
||||
this.resourceService.getInstitutionNameFunction();
|
||||
|
||||
// table
|
||||
final EntityTable<QuizData> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetQuizPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
|
||||
.withColumnIf(
|
||||
isSebAdmin,
|
||||
() -> new ColumnDefinition<QuizData>(
|
||||
QuizData.QUIZ_ATTR_INSTITUION_ID,
|
||||
INSTITUION_TEXT_KEY,
|
||||
quiz -> institutionNameFunction
|
||||
.apply(String.valueOf(quiz.institutionId)))
|
||||
.withFilter(this.institutionFilter))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_LMS_SETUP_ID,
|
||||
LMS_TEXT_KEY,
|
||||
quizDataLmsSetupNameFunction(this.resourceService))
|
||||
.withFilter(this.lmsFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
NAME_TEXT_KEY,
|
||||
QuizData::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
new LocTextKey(
|
||||
START_TIME_TEXT_KEY.name,
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
QuizData::getStartTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE,
|
||||
QuizData.FILTER_ATTR_START_TIME,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
new LocTextKey(
|
||||
END_TIME_TEXT_KEY.name,
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
QuizData::getEndTime)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
|
||||
.withExec(action -> this.showDetails(
|
||||
action,
|
||||
t.getSingleSelectedROWData(),
|
||||
institutionNameFunction))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
final GrantCheck examGrant = currentUser.grantCheck(EntityType.EXAM);
|
||||
actionBuilder
|
||||
|
||||
// Removed as discussed in SEBSERV-52
|
||||
// .newAction(ActionDefinition.LMS_SETUP_NEW)
|
||||
// .publishIf(lmsSetupGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
action -> this.showDetails(
|
||||
action,
|
||||
table.getSingleSelectedROWData(),
|
||||
institutionNameFunction),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
.publishIf(table::hasAnyContent)
|
||||
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
action -> this.importQuizData(action, table),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.publishIf(() -> examGrant.im() && table.hasAnyContent());
|
||||
}
|
||||
|
||||
private static Function<QuizData, String> quizDataLmsSetupNameFunction(final ResourceService resourceService) {
|
||||
return quizzData -> resourceService.getLmsSetupNameFunction()
|
||||
.apply(String.valueOf(quizzData.lmsSetupId));
|
||||
}
|
||||
|
||||
private PageAction importQuizData(final PageAction action, final EntityTable<QuizData> table) {
|
||||
action.getSingleSelection();
|
||||
final QuizData selectedROWData = table.getSingleSelectedROWData();
|
||||
|
||||
if (selectedROWData.endTime != null) {
|
||||
final DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
if (selectedROWData.endTime.isBefore(now)) {
|
||||
throw new PageMessageException(NO_IMPORT_OF_OUT_DATED_QUIZ);
|
||||
}
|
||||
}
|
||||
|
||||
return action
|
||||
.withEntityKey(action.getSingleSelection())
|
||||
.withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, "true");
|
||||
}
|
||||
|
||||
private PageAction showDetails(
|
||||
final PageAction action,
|
||||
final QuizData quizData,
|
||||
final Function<String, String> institutionNameFunction) {
|
||||
|
||||
action.getSingleSelection();
|
||||
|
||||
final ModalInputDialog<Void> dialog = new ModalInputDialog<Void>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.widgetFactory)
|
||||
.setLargeDialogWidth();
|
||||
|
||||
dialog.open(
|
||||
DETAILS_TITLE_TEXT_KEY,
|
||||
action.pageContext(),
|
||||
pc -> createDetailsForm(quizData, pc, institutionNameFunction));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static final Collection<String> ADDITIONAL_HTML_ATTRIBUTES = Arrays.asList(
|
||||
"course_summary");
|
||||
|
||||
private void createDetailsForm(
|
||||
final QuizData quizData,
|
||||
final PageContext pc,
|
||||
final Function<String, String> institutionNameFunction) {
|
||||
|
||||
final Composite parent = pc.getParent();
|
||||
final Composite grid = this.widgetFactory.createPopupScrollComposite(parent);
|
||||
|
||||
final FormBuilder formbuilder = this.pageService.formBuilder(pc.copyOf(grid))
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addFieldIf(
|
||||
() -> this.resourceService.getCurrentUser().get().hasRole(UserRole.SEB_SERVER_ADMIN),
|
||||
() -> FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_INSTITUION_ID,
|
||||
INSTITUION_TEXT_KEY,
|
||||
institutionNameFunction.apply(quizData.getModelId())))
|
||||
.addField(FormBuilder.singleSelection(
|
||||
QuizData.QUIZ_ATTR_LMS_SETUP_ID,
|
||||
LMS_TEXT_KEY,
|
||||
String.valueOf(quizData.lmsSetupId),
|
||||
() -> this.resourceService.lmsSetupResource()))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
NAME_TEXT_KEY,
|
||||
quizData.name))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_DESCRIPTION,
|
||||
QUIZ_DETAILS_DESCRIPTION_TEXT_KEY,
|
||||
quizData.description)
|
||||
.asHTML())
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
QUIZ_DETAILS_START_TIME_TEXT_KEY,
|
||||
this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.startTime)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
QUIZ_DETAILS_END_TIME_TEXT_KEY,
|
||||
this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.endTime)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_START_URL,
|
||||
QUIZ_DETAILS_URL_TEXT_KEY,
|
||||
quizData.startURL));
|
||||
|
||||
if (!quizData.additionalAttributes.isEmpty()) {
|
||||
quizData.additionalAttributes
|
||||
.entrySet()
|
||||
.stream()
|
||||
.forEach(entry -> {
|
||||
LocTextKey titleKey = new LocTextKey(TEXT_KEY_ADDITIONAL_ATTR_PREFIX + entry.getKey());
|
||||
if (!this.pageService.getI18nSupport().hasText(titleKey)) {
|
||||
titleKey = new LocTextKey(entry.getKey());
|
||||
}
|
||||
formbuilder
|
||||
.addField(FormBuilder.text(
|
||||
entry.getKey(),
|
||||
titleKey,
|
||||
toAdditionalValue(entry.getKey(), entry.getValue()))
|
||||
.asHTML(ADDITIONAL_HTML_ATTRIBUTES.contains(entry.getKey())));
|
||||
});
|
||||
}
|
||||
|
||||
formbuilder.build();
|
||||
}
|
||||
|
||||
private String toAdditionalValue(final String name, final String value) {
|
||||
if ("timecreated".equals(name)) {
|
||||
try {
|
||||
return this.pageService
|
||||
.getI18nSupport()
|
||||
.formatDisplayDate(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
|
||||
} catch (final Exception e) {
|
||||
return value;
|
||||
}
|
||||
} else if ("timelimit".equals(name)) {
|
||||
try {
|
||||
return this.pageService
|
||||
.getI18nSupport()
|
||||
.formatDisplayTime(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
|
||||
} catch (final Exception e) {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
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.TemplateComposer;
|
||||
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.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class QuizDiscoveryList implements TemplateComposer {
|
||||
|
||||
// localized text keys
|
||||
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.title");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.empty");
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT =
|
||||
new LocTextKey("sebserver.quizdiscovery.info.pleaseSelect");
|
||||
private final static LocTextKey INSTITUTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.institution");
|
||||
private final static LocTextKey LMS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.lmssetup");
|
||||
private final static LocTextKey NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.name");
|
||||
private final static LocTextKey START_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.starttime");
|
||||
private final static LocTextKey END_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.list.column.endtime");
|
||||
private final static LocTextKey DETAILS_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.title");
|
||||
private static final LocTextKey QUIZ_DETAILS_URL_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.url");
|
||||
private static final LocTextKey QUIZ_DETAILS_INSTITUTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.institution");
|
||||
private static final LocTextKey QUIZ_DETAILS_LMS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.lmssetup");
|
||||
private static final LocTextKey QUIZ_DETAILS_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.name");
|
||||
private static final LocTextKey QUIZ_DETAILS_DESCRIPTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.description");
|
||||
private static final LocTextKey QUIZ_DETAILS_START_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.starttime");
|
||||
private static final LocTextKey QUIZ_DETAILS_END_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.details.endtime");
|
||||
private final static LocTextKey NO_IMPORT_OF_OUT_DATED_QUIZ =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
|
||||
|
||||
private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX =
|
||||
"sebserver.quizdiscovery.quiz.details.additional.";
|
||||
|
||||
// filter attribute models
|
||||
private final TableFilterAttribute institutionFilter;
|
||||
private final TableFilterAttribute lmsFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
|
||||
// dependencies
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final ResourceService resourceService;
|
||||
private final PageService pageService;
|
||||
private final int pageSize;
|
||||
|
||||
protected QuizDiscoveryList(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.resourceService = resourceService;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.institutionFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Entity.FILTER_ATTR_INSTITUTION,
|
||||
this.resourceService::institutionResource);
|
||||
|
||||
this.lmsFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
LmsSetup.FILTER_ATTR_LMS_SETUP,
|
||||
this.resourceService::lmsSetupResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = this.widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
TITLE_TEXT_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder =
|
||||
this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final BooleanSupplier isSebAdmin =
|
||||
() -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
|
||||
|
||||
final Function<String, String> institutionNameFunction =
|
||||
this.resourceService.getInstitutionNameFunction();
|
||||
|
||||
// table
|
||||
final EntityTable<QuizData> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetQuizPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
|
||||
.withColumnIf(
|
||||
isSebAdmin,
|
||||
() -> new ColumnDefinition<QuizData>(
|
||||
QuizData.QUIZ_ATTR_INSTITUTION_ID,
|
||||
INSTITUTION_TEXT_KEY,
|
||||
quiz -> institutionNameFunction
|
||||
.apply(String.valueOf(quiz.institutionId)))
|
||||
.withFilter(this.institutionFilter))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_LMS_SETUP_ID,
|
||||
LMS_TEXT_KEY,
|
||||
quizDataLmsSetupNameFunction(this.resourceService))
|
||||
.withFilter(this.lmsFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
NAME_TEXT_KEY,
|
||||
QuizData::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
new LocTextKey(
|
||||
START_TIME_TEXT_KEY.name,
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
QuizData::getStartTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE,
|
||||
QuizData.FILTER_ATTR_START_TIME,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
new LocTextKey(
|
||||
END_TIME_TEXT_KEY.name,
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
QuizData::getEndTime)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
|
||||
.withExec(action -> this.showDetails(
|
||||
action,
|
||||
t.getSingleSelectedROWData(),
|
||||
institutionNameFunction))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS,
|
||||
ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
final GrantCheck examGrant = currentUser.grantCheck(EntityType.EXAM);
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
action -> this.showDetails(
|
||||
action,
|
||||
table.getSingleSelectedROWData(),
|
||||
institutionNameFunction),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
.publishIf(table::hasAnyContent, false)
|
||||
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
action -> this.importQuizData(action, table),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.publishIf(() -> examGrant.im() && table.hasAnyContent(), false);
|
||||
}
|
||||
|
||||
private static Function<QuizData, String> quizDataLmsSetupNameFunction(final ResourceService resourceService) {
|
||||
return quizzData -> resourceService.getLmsSetupNameFunction()
|
||||
.apply(String.valueOf(quizzData.lmsSetupId));
|
||||
}
|
||||
|
||||
private PageAction importQuizData(final PageAction action, final EntityTable<QuizData> table) {
|
||||
action.getSingleSelection();
|
||||
final QuizData selectedROWData = table.getSingleSelectedROWData();
|
||||
|
||||
if (selectedROWData.endTime != null) {
|
||||
final DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
if (selectedROWData.endTime.isBefore(now)) {
|
||||
throw new PageMessageException(NO_IMPORT_OF_OUT_DATED_QUIZ);
|
||||
}
|
||||
}
|
||||
|
||||
return action
|
||||
.withEntityKey(action.getSingleSelection())
|
||||
.withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, Constants.TRUE_STRING);
|
||||
}
|
||||
|
||||
private PageAction showDetails(
|
||||
final PageAction action,
|
||||
final QuizData quizData,
|
||||
final Function<String, String> institutionNameFunction) {
|
||||
|
||||
action.getSingleSelection();
|
||||
|
||||
final ModalInputDialog<Void> dialog = new ModalInputDialog<Void>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.widgetFactory)
|
||||
.setLargeDialogWidth();
|
||||
|
||||
dialog.open(
|
||||
DETAILS_TITLE_TEXT_KEY,
|
||||
action.pageContext(),
|
||||
pc -> createDetailsForm(quizData, pc, institutionNameFunction));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static final Collection<String> ADDITIONAL_HTML_ATTRIBUTES = Arrays.asList(
|
||||
"course_summary");
|
||||
|
||||
private void createDetailsForm(
|
||||
final QuizData quizData,
|
||||
final PageContext pc,
|
||||
final Function<String, String> institutionNameFunction) {
|
||||
|
||||
final Composite parent = pc.getParent();
|
||||
final Composite grid = this.widgetFactory.createPopupScrollComposite(parent);
|
||||
|
||||
final FormBuilder formbuilder = this.pageService.formBuilder(pc.copyOf(grid))
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addFieldIf(
|
||||
() -> this.resourceService.getCurrentUser().get().hasRole(UserRole.SEB_SERVER_ADMIN),
|
||||
() -> FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_INSTITUTION_ID,
|
||||
QUIZ_DETAILS_INSTITUTION_TEXT_KEY,
|
||||
institutionNameFunction.apply(quizData.getModelId())))
|
||||
.addField(FormBuilder.singleSelection(
|
||||
QuizData.QUIZ_ATTR_LMS_SETUP_ID,
|
||||
QUIZ_DETAILS_LMS_TEXT_KEY,
|
||||
String.valueOf(quizData.lmsSetupId),
|
||||
this.resourceService::lmsSetupResource))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
QUIZ_DETAILS_NAME_TEXT_KEY,
|
||||
quizData.name))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_DESCRIPTION,
|
||||
QUIZ_DETAILS_DESCRIPTION_TEXT_KEY,
|
||||
quizData.description)
|
||||
.asHTML())
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
QUIZ_DETAILS_START_TIME_TEXT_KEY,
|
||||
this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.startTime)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
QUIZ_DETAILS_END_TIME_TEXT_KEY,
|
||||
this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.endTime)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_START_URL,
|
||||
QUIZ_DETAILS_URL_TEXT_KEY,
|
||||
quizData.startURL));
|
||||
|
||||
if (!quizData.additionalAttributes.isEmpty()) {
|
||||
quizData.additionalAttributes
|
||||
.forEach((key, value) -> {
|
||||
LocTextKey titleKey = new LocTextKey(TEXT_KEY_ADDITIONAL_ATTR_PREFIX + key);
|
||||
if (!this.pageService.getI18nSupport().hasText(titleKey)) {
|
||||
titleKey = new LocTextKey(key);
|
||||
}
|
||||
formbuilder
|
||||
.addField(FormBuilder.text(
|
||||
key,
|
||||
titleKey,
|
||||
toAdditionalValue(key, value))
|
||||
.asHTML(ADDITIONAL_HTML_ATTRIBUTES.contains(key)));
|
||||
});
|
||||
}
|
||||
|
||||
formbuilder.build();
|
||||
}
|
||||
|
||||
private String toAdditionalValue(final String name, final String value) {
|
||||
if (QuizData.ATTR_ADDITIONAL_CREATION_TIME.equals(name)) {
|
||||
try {
|
||||
return this.pageService
|
||||
.getI18nSupport()
|
||||
.formatDisplayDate(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
|
||||
} catch (final Exception e) {
|
||||
return value;
|
||||
}
|
||||
} else if (QuizData.ATTR_ADDITIONAL_TIME_LIMIT.equals(name)) {
|
||||
try {
|
||||
return this.pageService
|
||||
.getI18nSupport()
|
||||
.formatDisplayTime(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
|
||||
} catch (final Exception e) {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.gui.content;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
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.LocTextKey;
|
||||
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.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnection;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SebClientLogDetailsPopup {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SebClientLogDetailsPopup.class);
|
||||
|
||||
private static final LocTextKey DETAILS_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.title");
|
||||
private static final LocTextKey DETAILS_EVENT_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.event.title");
|
||||
private static final LocTextKey DETAILS_CONNECTION_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.connection.title");
|
||||
private static final LocTextKey DETAILS_EXAM_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.exam.title");
|
||||
|
||||
private static final LocTextKey FORM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.type");
|
||||
private static final LocTextKey FORM_SERVERTIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.server-time");
|
||||
private static final LocTextKey FORM_CLIENTTIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.client-time");
|
||||
private static final LocTextKey FORM_VALUE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.value");
|
||||
private static final LocTextKey FORM_MESSAGE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.message");
|
||||
|
||||
private static final LocTextKey FORM_SESSION_ID_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.session-id");
|
||||
private static final LocTextKey FORM_ADDRESS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.address");
|
||||
private static final LocTextKey FORM_TOKEN_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.token");
|
||||
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.status");
|
||||
private static final LocTextKey FORM_EXAM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.name");
|
||||
private static final LocTextKey FORM_DESC_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.description");
|
||||
private static final LocTextKey FORM_EXAM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.type");
|
||||
private static final LocTextKey FORM_START_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.startTime");
|
||||
private static final LocTextKey FORM_END_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.endTime");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final RestService restService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final WidgetFactory widgetFactory;
|
||||
|
||||
public SebClientLogDetailsPopup(
|
||||
final PageService pageService,
|
||||
final WidgetFactory widgetFactory) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.widgetFactory = widgetFactory;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.restService = pageService.getRestService();
|
||||
this.i18nSupport = pageService.getI18nSupport();
|
||||
}
|
||||
|
||||
PageAction showDetails(final PageAction action, final ExtendedClientEvent clientEvent) {
|
||||
action.getSingleSelection();
|
||||
|
||||
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.widgetFactory);
|
||||
dialog.setDialogWidth(600);
|
||||
|
||||
dialog.open(
|
||||
DETAILS_TITLE_TEXT_KEY,
|
||||
action.pageContext(),
|
||||
pc -> createDetailsForm(clientEvent, pc));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private void createDetailsForm(final ExtendedClientEvent clientEvent, final PageContext pc) {
|
||||
|
||||
final Composite parent = pc.getParent();
|
||||
final Composite content = this.widgetFactory.createPopupScrollComposite(parent);
|
||||
|
||||
// Event Details Title
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
WidgetFactory.CustomVariant.TEXT_H3,
|
||||
DETAILS_EVENT_TILE_TEXT_KEY);
|
||||
|
||||
PageContext formContext = pc.copyOf(content);
|
||||
|
||||
this.pageService.formBuilder(formContext)
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.TYPE_NAME,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
this.resourceService.getEventTypeName(clientEvent)))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
|
||||
FORM_CLIENTTIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateTime(clientEvent.clientTime) + " " +
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||
FORM_SERVERTIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateTime(clientEvent.serverTime) + " " +
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
|
||||
FORM_VALUE_TEXT_KEY,
|
||||
(clientEvent.numValue != null)
|
||||
? String.valueOf(clientEvent.numValue)
|
||||
: Constants.EMPTY_NOTE))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_TEXT,
|
||||
FORM_MESSAGE_TEXT_KEY,
|
||||
clientEvent.text)
|
||||
.asArea())
|
||||
.build();
|
||||
|
||||
// SEB Client Connection Title
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
WidgetFactory.CustomVariant.TEXT_H3,
|
||||
DETAILS_CONNECTION_TILE_TEXT_KEY);
|
||||
|
||||
final ClientConnection connection = this.restService.getBuilder(GetClientConnection.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.connectionId))
|
||||
.call()
|
||||
.get(
|
||||
error -> log.error("Failed to get ClientConnection for id {}", clientEvent.connectionId, error),
|
||||
() -> ClientConnection.EMPTY_CLIENT_CONNECTION);
|
||||
|
||||
this.pageService.formBuilder(formContext)
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
FORM_SESSION_ID_TEXT_KEY,
|
||||
connection.userSessionId))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
|
||||
FORM_ADDRESS_TEXT_KEY,
|
||||
connection.clientAddress))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
||||
FORM_TOKEN_TEXT_KEY,
|
||||
connection.connectionToken))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
this.resourceService.localizedClientConnectionStatusName(connection.status)))
|
||||
.build();
|
||||
|
||||
// Exam Details Title
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
WidgetFactory.CustomVariant.TEXT_H3,
|
||||
DETAILS_EXAM_TILE_TEXT_KEY);
|
||||
|
||||
final Exam exam = this.restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.examId))
|
||||
.call()
|
||||
.get(
|
||||
error -> log.error("Failed to get Exam for id {}", clientEvent.examId, error),
|
||||
() -> Exam.EMPTY_EXAM);
|
||||
|
||||
this.pageService.formBuilder(formContext)
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
FORM_EXAM_NAME_TEXT_KEY,
|
||||
exam.name))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_DESCRIPTION,
|
||||
FORM_DESC_TEXT_KEY,
|
||||
exam.description))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
FORM_EXAM_TYPE_TEXT_KEY,
|
||||
this.resourceService.localizedExamTypeName(exam)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
FORM_START_TIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateWithTimeZone(exam.startTime)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
FORM_END_TIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateWithTimeZone(exam.endTime)))
|
||||
.build();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,387 +1,205 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
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.LocTextKey;
|
||||
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.PageActionBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
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.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnection;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SebClientLogs implements TemplateComposer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SebClientLogs.class);
|
||||
|
||||
private static final LocTextKey DETAILS_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.title");
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.title");
|
||||
private static final LocTextKey EMPTY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.empty");
|
||||
|
||||
private static final LocTextKey EXAM_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.exam");
|
||||
private static final LocTextKey CLIENT_SESSION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.client-session");
|
||||
private static final LocTextKey TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.type");
|
||||
private static final LocTextKey TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.time");
|
||||
private static final LocTextKey VALUE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.value");
|
||||
|
||||
private static final LocTextKey DETAILS_EVENT_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.event.title");
|
||||
private static final LocTextKey DETAILS_CONNECTION_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.connection.title");
|
||||
private static final LocTextKey DETAILS_EXAM_TILE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.details.exam.title");
|
||||
|
||||
private static final LocTextKey FORM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.type");
|
||||
private static final LocTextKey FORM_SERVERTIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.server-time");
|
||||
private static final LocTextKey FORM_CLIENTTIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.client-time");
|
||||
private static final LocTextKey FORM_VALUE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.value");
|
||||
private static final LocTextKey FORM_MESSAGE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.message");
|
||||
|
||||
private static final LocTextKey FORM_SESSION_ID_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.session-id");
|
||||
private static final LocTextKey FORM_ADDRESS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.address");
|
||||
private static final LocTextKey FORM_TOKEN_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.token");
|
||||
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.connection.status");
|
||||
|
||||
private static final LocTextKey FORM_EXAM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.name");
|
||||
private static final LocTextKey FORM_DESC_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.description");
|
||||
private static final LocTextKey FORM_EXAM_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.type");
|
||||
private static final LocTextKey FORM_START_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.startTime");
|
||||
private static final LocTextKey FORM_END_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.form.column.exam.endTime");
|
||||
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT =
|
||||
new LocTextKey("sebserver.seblogs.info.pleaseSelect");
|
||||
|
||||
private final TableFilterAttribute examFilter;
|
||||
private final TableFilterAttribute clientSessionFilter;
|
||||
private final TableFilterAttribute eventTypeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final RestService restService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final int pageSize;
|
||||
|
||||
public SebClientLogs(
|
||||
final PageService pageService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.restService = this.resourceService.getRestService();
|
||||
this.i18nSupport = this.resourceService.getI18nSupport();
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.examFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
ExtendedClientEvent.FILTER_ATTRIBUTE_EXAM,
|
||||
this.resourceService::getExamLogSelectionResources);
|
||||
|
||||
this.clientSessionFilter = new TableFilterAttribute(
|
||||
CriteriaType.TEXT,
|
||||
ClientConnection.FILTER_ATTR_SESSION_ID);
|
||||
|
||||
this.eventTypeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
ClientEvent.FILTER_ATTR_TYPE,
|
||||
this.resourceService::clientEventTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
TITLE_TEXT_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(
|
||||
pageContext
|
||||
.clearEntityKeys()
|
||||
.clearAttributes());
|
||||
|
||||
// table
|
||||
final EntityTable<ExtendedClientEvent> table = this.pageService.entityTableBuilder(
|
||||
this.restService.getRestCall(GetExtendedClientEventPage.class))
|
||||
.withEmptyMessage(EMPTY_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_ID,
|
||||
EXAM_TEXT_KEY,
|
||||
examNameFunction())
|
||||
.withFilter(this.examFilter)
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
CLIENT_SESSION_TEXT_KEY,
|
||||
ExtendedClientEvent::getUserSessionId)
|
||||
.withFilter(this.clientSessionFilter)
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.TYPE_NAME,
|
||||
TYPE_TEXT_KEY,
|
||||
this.resourceService::getEventTypeName)
|
||||
.withFilter(this.eventTypeFilter)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||
new LocTextKey(
|
||||
TIME_TEXT_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getEventTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE_TIME_RANGE,
|
||||
ClientEvent.FILTER_ATTR_SERVER_TIME_FROM_TO,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
|
||||
VALUE_TEXT_KEY,
|
||||
clientEvent -> (clientEvent.numValue != null)
|
||||
? String.valueOf(clientEvent.numValue)
|
||||
: Constants.EMPTY_NOTE)
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
|
||||
.withExec(action -> this.showDetails(action, t.getSingleSelectedROWData()))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
action -> this.showDetails(action, table.getSingleSelectedROWData()),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
.publishIf(table::hasAnyContent);
|
||||
}
|
||||
|
||||
private PageAction showDetails(final PageAction action, final ExtendedClientEvent clientEvent) {
|
||||
action.getSingleSelection();
|
||||
|
||||
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.widgetFactory);
|
||||
dialog.setDialogWidth(600);
|
||||
|
||||
dialog.open(
|
||||
DETAILS_TITLE_TEXT_KEY,
|
||||
action.pageContext(),
|
||||
pc -> createDetailsForm(clientEvent, pc));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private void createDetailsForm(final ExtendedClientEvent clientEvent, final PageContext pc) {
|
||||
|
||||
final Composite parent = pc.getParent();
|
||||
final Composite content = this.widgetFactory.createPopupScrollComposite(parent);
|
||||
|
||||
// Event Details Title
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
DETAILS_EVENT_TILE_TEXT_KEY);
|
||||
|
||||
this.pageService.formBuilder(pc.copyOf(content))
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.TYPE_NAME,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
this.resourceService.getEventTypeName(clientEvent)))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
|
||||
FORM_CLIENTTIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateTime(clientEvent.clientTime) + " " +
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||
FORM_SERVERTIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateTime(clientEvent.serverTime) + " " +
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
|
||||
FORM_VALUE_TEXT_KEY,
|
||||
(clientEvent.numValue != null)
|
||||
? String.valueOf(clientEvent.numValue)
|
||||
: Constants.EMPTY_NOTE))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_EVENT.ATTR_TEXT,
|
||||
FORM_MESSAGE_TEXT_KEY,
|
||||
clientEvent.text)
|
||||
.asArea())
|
||||
.build();
|
||||
|
||||
// SEB Client Connection Title
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
DETAILS_CONNECTION_TILE_TEXT_KEY);
|
||||
|
||||
final ClientConnection connection = this.restService.getBuilder(GetClientConnection.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.connectionId))
|
||||
.call()
|
||||
.get(
|
||||
error -> log.error("Failed to get ClientConnection for id {}", clientEvent.connectionId, error),
|
||||
() -> ClientConnection.EMPTY_CLIENT_CONNECTION);
|
||||
|
||||
this.pageService.formBuilder(pc.copyOf(content))
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
FORM_SESSION_ID_TEXT_KEY,
|
||||
connection.userSessionId))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
|
||||
FORM_ADDRESS_TEXT_KEY,
|
||||
connection.clientAddress))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
||||
FORM_TOKEN_TEXT_KEY,
|
||||
connection.connectionToken))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
this.resourceService.localizedClientConnectionStatusName(connection.status)))
|
||||
.build();
|
||||
|
||||
// Exam Details Title
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
DETAILS_EXAM_TILE_TEXT_KEY);
|
||||
|
||||
final Exam exam = this.restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.examId))
|
||||
.call()
|
||||
.get(
|
||||
error -> log.error("Failed to get Exam for id {}", clientEvent.examId, error),
|
||||
() -> Exam.EMPTY_EXAM);
|
||||
|
||||
this.pageService.formBuilder(pc.copyOf(content))
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
FORM_EXAM_NAME_TEXT_KEY,
|
||||
exam.name))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_DESCRIPTION,
|
||||
FORM_DESC_TEXT_KEY,
|
||||
exam.description))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
FORM_EXAM_TYPE_TEXT_KEY,
|
||||
this.resourceService.localizedExamTypeName(exam)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
FORM_START_TIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateWithTimeZone(exam.startTime)))
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
FORM_END_TIME_TEXT_KEY,
|
||||
this.i18nSupport.formatDisplayDateWithTimeZone(exam.endTime)))
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
private Function<ExtendedClientEvent, String> examNameFunction() {
|
||||
final Map<Long, String> examNameMapping = this.resourceService.getExamNameMapping();
|
||||
return event -> examNameMapping.get(event.examId);
|
||||
}
|
||||
|
||||
private final String getEventTime(final ExtendedClientEvent event) {
|
||||
if (event == null || event.serverTime == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayDateTime(Utils.toDateTimeUTC(event.serverTime));
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.LocTextKey;
|
||||
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.PageActionBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SebClientLogs implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.title");
|
||||
private static final LocTextKey EMPTY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.empty");
|
||||
|
||||
private static final LocTextKey EXAM_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.exam");
|
||||
private static final LocTextKey CLIENT_SESSION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.client-session");
|
||||
private static final LocTextKey TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.type");
|
||||
private static final LocTextKey TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.time");
|
||||
private static final LocTextKey VALUE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.seblogs.list.column.value");
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT =
|
||||
new LocTextKey("sebserver.seblogs.info.pleaseSelect");
|
||||
|
||||
private final TableFilterAttribute examFilter;
|
||||
private final TableFilterAttribute clientSessionFilter;
|
||||
private final TableFilterAttribute eventTypeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final RestService restService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final SebClientLogDetailsPopup sebClientLogDetailsPopup;
|
||||
private final int pageSize;
|
||||
|
||||
public SebClientLogs(
|
||||
final PageService pageService,
|
||||
final SebClientLogDetailsPopup sebClientLogDetailsPopup,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.restService = this.resourceService.getRestService();
|
||||
this.i18nSupport = this.resourceService.getI18nSupport();
|
||||
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.examFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
ExtendedClientEvent.FILTER_ATTRIBUTE_EXAM,
|
||||
this.resourceService::getExamLogSelectionResources);
|
||||
|
||||
this.clientSessionFilter = new TableFilterAttribute(
|
||||
CriteriaType.TEXT,
|
||||
ClientConnection.FILTER_ATTR_SESSION_ID);
|
||||
|
||||
this.eventTypeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
ClientEvent.FILTER_ATTR_TYPE,
|
||||
this.resourceService::clientEventTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
TITLE_TEXT_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(
|
||||
pageContext
|
||||
.clearEntityKeys()
|
||||
.clearAttributes());
|
||||
|
||||
// table
|
||||
final EntityTable<ExtendedClientEvent> table = this.pageService.entityTableBuilder(
|
||||
this.restService.getRestCall(GetExtendedClientEventPage.class))
|
||||
.withEmptyMessage(EMPTY_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_ID,
|
||||
EXAM_TEXT_KEY,
|
||||
examNameFunction())
|
||||
.withFilter(this.examFilter)
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
CLIENT_SESSION_TEXT_KEY,
|
||||
ExtendedClientEvent::getUserSessionId)
|
||||
.withFilter(this.clientSessionFilter)
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.TYPE_NAME,
|
||||
TYPE_TEXT_KEY,
|
||||
this.resourceService::getEventTypeName)
|
||||
.withFilter(this.eventTypeFilter)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||
new LocTextKey(
|
||||
TIME_TEXT_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getEventTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE_TIME_RANGE,
|
||||
ClientEvent.FILTER_ATTR_SERVER_TIME_FROM_TO,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
|
||||
VALUE_TEXT_KEY,
|
||||
clientEvent -> (clientEvent.numValue != null)
|
||||
? String.valueOf(clientEvent.numValue)
|
||||
: Constants.EMPTY_NOTE)
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
|
||||
.withExec(action -> sebClientLogDetailsPopup.showDetails(action, t.getSingleSelectedROWData()))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
action -> sebClientLogDetailsPopup.showDetails(action, table.getSingleSelectedROWData()),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
.publishIf(table::hasAnyContent, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Function<ExtendedClientEvent, String> examNameFunction() {
|
||||
final Map<Long, String> examNameMapping = this.resourceService.getExamNameMapping();
|
||||
return event -> examNameMapping.get(event.examId);
|
||||
}
|
||||
|
||||
private String getEventTime(final ExtendedClientEvent event) {
|
||||
if (event == null || event.serverTime == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayDateTime(Utils.toDateTimeUTC(event.serverTime));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,194 +1,194 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCreationInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
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.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.CopyConfiguration;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
|
||||
|
||||
final class SebExamConfigCreationPopup {
|
||||
|
||||
static final LocTextKey FORM_COPY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.action.copy.dialog");
|
||||
static final LocTextKey FORM_COPY_AS_TEMPLATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.action.copy-as-template.dialog");
|
||||
static final LocTextKey FORM_CREATE_FROM_TEMPLATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.configtemplate.action.create-config.dialog");
|
||||
|
||||
static Function<PageAction, PageAction> configCreationFunction(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
final boolean copyAsTemplate = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(PageContext.AttributeKeys.COPY_AS_TEMPLATE));
|
||||
final boolean createFromTemplate = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(PageContext.AttributeKeys.CREATE_FROM_TEMPLATE));
|
||||
|
||||
return action -> {
|
||||
|
||||
final ModalInputDialog<FormHandle<ConfigCreationInfo>> dialog =
|
||||
new ModalInputDialog<FormHandle<ConfigCreationInfo>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setLargeDialogWidth();
|
||||
|
||||
final CreationFormContext formContext = new CreationFormContext(
|
||||
pageService,
|
||||
pageContext,
|
||||
copyAsTemplate,
|
||||
createFromTemplate);
|
||||
|
||||
final Predicate<FormHandle<ConfigCreationInfo>> doCopy = formHandle -> doCreate(
|
||||
pageService,
|
||||
pageContext,
|
||||
copyAsTemplate,
|
||||
createFromTemplate,
|
||||
formHandle);
|
||||
|
||||
final LocTextKey title = (copyAsTemplate)
|
||||
? FORM_COPY_AS_TEMPLATE_TEXT_KEY
|
||||
: (createFromTemplate)
|
||||
? FORM_CREATE_FROM_TEMPLATE_TEXT_KEY
|
||||
: FORM_COPY_TEXT_KEY;
|
||||
|
||||
dialog.open(
|
||||
title,
|
||||
doCopy,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
formContext);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static final boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final boolean copyAsTemplate,
|
||||
final boolean createFromTemplate,
|
||||
final FormHandle<ConfigCreationInfo> formHandle) {
|
||||
|
||||
// create either a new configuration form template or from other configuration
|
||||
final Class<? extends RestCall<ConfigurationNode>> restCall = (createFromTemplate)
|
||||
? NewExamConfig.class
|
||||
: CopyConfiguration.class;
|
||||
|
||||
final ConfigurationNode newConfig = pageService
|
||||
.getRestService()
|
||||
.getBuilder(restCall)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.getOr(null);
|
||||
|
||||
if (newConfig == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// view either new template or configuration
|
||||
final PageAction viewCopy = (copyAsTemplate)
|
||||
? pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW)
|
||||
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||
.create()
|
||||
: pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||
.create();
|
||||
|
||||
pageService.executePageAction(viewCopy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final class CreationFormContext implements ModalInputDialogComposer<FormHandle<ConfigCreationInfo>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
private final boolean copyAsTemplate;
|
||||
private final boolean createFromTemplate;
|
||||
|
||||
protected CreationFormContext(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final boolean copyAsTemplate,
|
||||
final boolean createFromTemplate) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
this.copyAsTemplate = copyAsTemplate;
|
||||
this.createFromTemplate = createFromTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<ConfigCreationInfo>> compose(final Composite parent) {
|
||||
|
||||
final Composite grid = this.pageService.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final FormHandle<ConfigCreationInfo> formHandle = this.pageService.formBuilder(
|
||||
this.pageContext.copyOf(grid))
|
||||
.readonly(false)
|
||||
.putStaticValueIf(
|
||||
() -> !this.createFromTemplate,
|
||||
Domain.CONFIGURATION_NODE.ATTR_ID,
|
||||
entityKey.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.CONFIGURATION_NODE.ATTR_TYPE,
|
||||
(this.copyAsTemplate)
|
||||
? ConfigurationType.TEMPLATE.name()
|
||||
: ConfigurationType.EXAM_CONFIG.name())
|
||||
.putStaticValueIf(
|
||||
() -> this.createFromTemplate,
|
||||
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
|
||||
entityKey.getModelId())
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_NAME,
|
||||
SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
|
||||
.asArea())
|
||||
.addFieldIf(
|
||||
() -> !this.copyAsTemplate && !this.createFromTemplate,
|
||||
() -> FormBuilder.checkbox(
|
||||
ConfigCreationInfo.ATTR_COPY_WITH_HISTORY,
|
||||
SebExamConfigPropForm.FORM_HISTORY_TEXT_KEY))
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCreationInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
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.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.CopyConfiguration;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
|
||||
|
||||
final class SebExamConfigCreationPopup {
|
||||
|
||||
static final LocTextKey FORM_COPY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.action.copy.dialog");
|
||||
static final LocTextKey FORM_COPY_AS_TEMPLATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.action.copy-as-template.dialog");
|
||||
static final LocTextKey FORM_CREATE_FROM_TEMPLATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.configtemplate.action.create-config.dialog");
|
||||
|
||||
static Function<PageAction, PageAction> configCreationFunction(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
final boolean copyAsTemplate = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(PageContext.AttributeKeys.COPY_AS_TEMPLATE));
|
||||
final boolean createFromTemplate = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(PageContext.AttributeKeys.CREATE_FROM_TEMPLATE));
|
||||
|
||||
return action -> {
|
||||
|
||||
final ModalInputDialog<FormHandle<ConfigCreationInfo>> dialog =
|
||||
new ModalInputDialog<FormHandle<ConfigCreationInfo>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setLargeDialogWidth();
|
||||
|
||||
final CreationFormContext formContext = new CreationFormContext(
|
||||
pageService,
|
||||
pageContext,
|
||||
copyAsTemplate,
|
||||
createFromTemplate);
|
||||
|
||||
final Predicate<FormHandle<ConfigCreationInfo>> doCopy = formHandle -> doCreate(
|
||||
pageService,
|
||||
pageContext,
|
||||
copyAsTemplate,
|
||||
createFromTemplate,
|
||||
formHandle);
|
||||
|
||||
final LocTextKey title = (copyAsTemplate)
|
||||
? FORM_COPY_AS_TEMPLATE_TEXT_KEY
|
||||
: (createFromTemplate)
|
||||
? FORM_CREATE_FROM_TEMPLATE_TEXT_KEY
|
||||
: FORM_COPY_TEXT_KEY;
|
||||
|
||||
dialog.open(
|
||||
title,
|
||||
doCopy,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
formContext);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final boolean copyAsTemplate,
|
||||
final boolean createFromTemplate,
|
||||
final FormHandle<ConfigCreationInfo> formHandle) {
|
||||
|
||||
// create either a new configuration form template or from other configuration
|
||||
final Class<? extends RestCall<ConfigurationNode>> restCall = (createFromTemplate)
|
||||
? NewExamConfig.class
|
||||
: CopyConfiguration.class;
|
||||
|
||||
final ConfigurationNode newConfig = pageService
|
||||
.getRestService()
|
||||
.getBuilder(restCall)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.getOr(null);
|
||||
|
||||
if (newConfig == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// view either new template or configuration
|
||||
final PageAction viewCopy = (copyAsTemplate)
|
||||
? pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW)
|
||||
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||
.create()
|
||||
: pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||
.create();
|
||||
|
||||
pageService.executePageAction(viewCopy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final class CreationFormContext implements ModalInputDialogComposer<FormHandle<ConfigCreationInfo>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
private final boolean copyAsTemplate;
|
||||
private final boolean createFromTemplate;
|
||||
|
||||
protected CreationFormContext(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final boolean copyAsTemplate,
|
||||
final boolean createFromTemplate) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
this.copyAsTemplate = copyAsTemplate;
|
||||
this.createFromTemplate = createFromTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<ConfigCreationInfo>> compose(final Composite parent) {
|
||||
|
||||
final Composite grid = this.pageService.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final FormHandle<ConfigCreationInfo> formHandle = this.pageService.formBuilder(
|
||||
this.pageContext.copyOf(grid))
|
||||
.readonly(false)
|
||||
.putStaticValueIf(
|
||||
() -> !this.createFromTemplate,
|
||||
Domain.CONFIGURATION_NODE.ATTR_ID,
|
||||
entityKey.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.CONFIGURATION_NODE.ATTR_TYPE,
|
||||
(this.copyAsTemplate)
|
||||
? ConfigurationType.TEMPLATE.name()
|
||||
: ConfigurationType.EXAM_CONFIG.name())
|
||||
.putStaticValueIf(
|
||||
() -> this.createFromTemplate,
|
||||
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
|
||||
entityKey.getModelId())
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_NAME,
|
||||
SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
|
||||
.asArea())
|
||||
.addFieldIf(
|
||||
() -> !this.copyAsTemplate && !this.createFromTemplate,
|
||||
() -> FormBuilder.checkbox(
|
||||
ConfigCreationInfo.ATTR_COPY_WITH_HISTORY,
|
||||
SebExamConfigPropForm.FORM_HISTORY_TEXT_KEY))
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -183,6 +183,11 @@ public enum ActionDefinition {
|
|||
ImageIcon.SAVE,
|
||||
PageStateDefinitionImpl.LMS_SETUP_VIEW,
|
||||
ActionCategory.FORM),
|
||||
LMS_SETUP_SAVE_AND_ACTIVATE(
|
||||
new LocTextKey("sebserver.form.action.save.activate"),
|
||||
ImageIcon.ACTIVE,
|
||||
PageStateDefinitionImpl.LMS_SETUP_VIEW,
|
||||
ActionCategory.FORM),
|
||||
LMS_SETUP_ACTIVATE(
|
||||
new LocTextKey("sebserver.lmssetup.action.activate"),
|
||||
ImageIcon.TOGGLE_OFF,
|
||||
|
@ -193,6 +198,11 @@ public enum ActionDefinition {
|
|||
ImageIcon.TOGGLE_ON,
|
||||
PageStateDefinitionImpl.LMS_SETUP_VIEW,
|
||||
ActionCategory.FORM),
|
||||
LMS_SETUP_TOGGLE_ACTIVITY(
|
||||
new LocTextKey("sebserver.overall.action.toggle-activity"),
|
||||
ImageIcon.SWITCH,
|
||||
PageStateDefinitionImpl.LMS_SETUP_LIST,
|
||||
ActionCategory.LMS_SETUP_LIST),
|
||||
|
||||
QUIZ_DISCOVERY_VIEW_LIST(
|
||||
new LocTextKey("sebserver.quizdiscovery.action.list"),
|
||||
|
@ -593,7 +603,7 @@ public enum ActionDefinition {
|
|||
ImageIcon.SEND_QUIT,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FORM),
|
||||
MONITOR_EXAM_FROM_DETAILS(
|
||||
MONITOR_EXAM_BACK_TO_OVERVIEW(
|
||||
new LocTextKey("sebserver.monitoring.exam.action.detail.view"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
|
|
|
@ -1,185 +1,185 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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.gui.form;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form.FormFieldAccessor;
|
||||
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.FieldValidationError;
|
||||
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.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
|
||||
public class FormHandle<T extends Entity> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
|
||||
|
||||
public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError.";
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
private final Form form;
|
||||
private final RestCall<T> post;
|
||||
private final I18nSupport i18nSupport;
|
||||
|
||||
FormHandle(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final Form form,
|
||||
final RestCall<T> post) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
this.form = form;
|
||||
this.post = post;
|
||||
this.i18nSupport = pageService.getI18nSupport();
|
||||
}
|
||||
|
||||
public PageContext getContext() {
|
||||
return this.pageContext;
|
||||
}
|
||||
|
||||
public FormBinding getFormBinding() {
|
||||
return this.form;
|
||||
}
|
||||
|
||||
public Form getForm() {
|
||||
return this.form;
|
||||
}
|
||||
|
||||
/** Process an API post request to send and save the form field values
|
||||
* to the webservice and publishes a page event to return to read-only-view
|
||||
* to indicate that the data was successfully saved or process an validation
|
||||
* error indication if there are some validation errors.
|
||||
*
|
||||
* @param action the save action context
|
||||
* @return the new Action context for read-only-view */
|
||||
public final PageAction processFormSave(final PageAction action) {
|
||||
return handleFormPost(doAPIPost(), action);
|
||||
}
|
||||
|
||||
public final PageAction saveAndActivate(final PageAction action) {
|
||||
final PageAction handleFormPost = handleFormPost(doAPIPost(), action);
|
||||
final EntityType entityType = this.post.getEntityType();
|
||||
this.pageService.getRestService().getBuilder(entityType, CallType.ACTIVATION_ACTIVATE)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, handleFormPost.getEntityKey().getModelId())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
return handleFormPost;
|
||||
}
|
||||
|
||||
/** process a form post by first resetting all field validation errors (if there are some)
|
||||
* then collecting all input data from the form by form-binding to a either a JSON string in
|
||||
* HTTP PUT case or to an form-URL-encoded string on HTTP POST case. And PUT or POST the data
|
||||
* to the webservice by using the defined RestCall and return the response result of the RestCall.
|
||||
*
|
||||
* @return the response result of the post (or put) RestCall */
|
||||
public Result<T> doAPIPost() {
|
||||
// reset all errors that may still be displayed
|
||||
this.form.process(
|
||||
Utils.truePredicate(),
|
||||
fieldAccessor -> fieldAccessor.resetError());
|
||||
|
||||
// post
|
||||
return this.post
|
||||
.newBuilder()
|
||||
.withFormBinding(this.form)
|
||||
.call();
|
||||
}
|
||||
|
||||
/** Uses the result of a form post to either create and publish a new Action to
|
||||
* go to the read-only-view of the specified form to indicate a successful form post
|
||||
* or stay within the edit-mode of the form and indicate errors or field validation messages
|
||||
* to the user on error case.
|
||||
*
|
||||
* @param postResult The form post result
|
||||
* @param action the action that was applied with the form post
|
||||
* @return the new Action that was used to stay on page or go the read-only-view of the form */
|
||||
public PageAction handleFormPost(final Result<T> postResult, final PageAction action) {
|
||||
return postResult
|
||||
.map(result -> {
|
||||
|
||||
PageAction resultAction = this.pageService.pageActionBuilder(action.pageContext())
|
||||
.newAction(action.definition)
|
||||
.create();
|
||||
if (resultAction.getEntityKey() == null) {
|
||||
resultAction = resultAction.withEntityKey(result.getEntityKey());
|
||||
}
|
||||
|
||||
return resultAction;
|
||||
})
|
||||
.onError(this::handleError)
|
||||
.getOrThrow(error -> new FormPostException(error));
|
||||
}
|
||||
|
||||
public boolean handleError(final Exception error) {
|
||||
if (error instanceof RestCallError) {
|
||||
((RestCallError) error)
|
||||
.getErrorMessages()
|
||||
.stream()
|
||||
.filter(APIMessage.ErrorMessage.FIELD_VALIDATION::isOf)
|
||||
.map(FieldValidationError::new)
|
||||
.forEach(fve -> this.form.process(
|
||||
name -> name.equals(fve.fieldName),
|
||||
fieldAccessor -> showValidationError(fieldAccessor, fve)));
|
||||
return true;
|
||||
} else {
|
||||
log.error("Unexpected error while trying to post form: {}", error.getMessage());
|
||||
final EntityType resultType = this.post.getEntityType();
|
||||
if (resultType != null) {
|
||||
this.pageContext.notifySaveError(resultType, error);
|
||||
} else {
|
||||
this.pageContext.notifyError(
|
||||
new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, Constants.EMPTY_NOTE),
|
||||
error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAnyError() {
|
||||
return this.form.hasAnyError();
|
||||
}
|
||||
|
||||
private final void showValidationError(
|
||||
final FormFieldAccessor fieldAccessor,
|
||||
final FieldValidationError valError) {
|
||||
|
||||
fieldAccessor.setError(this.i18nSupport.getText(new LocTextKey(
|
||||
FIELD_VALIDATION_LOCTEXT_PREFIX + valError.errorType,
|
||||
(Object[]) valError.getAttributes())));
|
||||
}
|
||||
|
||||
public FormHandle<T> process(
|
||||
final Predicate<String> nameFilter,
|
||||
final Consumer<FormFieldAccessor> processor) {
|
||||
|
||||
this.form.process(nameFilter, processor);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2018 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.gui.form;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form.FormFieldAccessor;
|
||||
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.FieldValidationError;
|
||||
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.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
|
||||
public class FormHandle<T extends Entity> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
|
||||
|
||||
public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError.";
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
private final Form form;
|
||||
private final RestCall<T> post;
|
||||
private final I18nSupport i18nSupport;
|
||||
|
||||
FormHandle(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final Form form,
|
||||
final RestCall<T> post) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
this.form = form;
|
||||
this.post = post;
|
||||
this.i18nSupport = pageService.getI18nSupport();
|
||||
}
|
||||
|
||||
public PageContext getContext() {
|
||||
return this.pageContext;
|
||||
}
|
||||
|
||||
public FormBinding getFormBinding() {
|
||||
return this.form;
|
||||
}
|
||||
|
||||
public Form getForm() {
|
||||
return this.form;
|
||||
}
|
||||
|
||||
/** Process an API post request to send and save the form field values
|
||||
* to the webservice and publishes a page event to return to read-only-view
|
||||
* to indicate that the data was successfully saved or process an validation
|
||||
* error indication if there are some validation errors.
|
||||
*
|
||||
* @param action the save action context
|
||||
* @return the new Action context for read-only-view */
|
||||
public final PageAction processFormSave(final PageAction action) {
|
||||
return handleFormPost(doAPIPost(), action);
|
||||
}
|
||||
|
||||
public final PageAction saveAndActivate(final PageAction action) {
|
||||
final PageAction handleFormPost = handleFormPost(doAPIPost(), action);
|
||||
final EntityType entityType = this.post.getEntityType();
|
||||
this.pageService.getRestService().getBuilder(entityType, CallType.ACTIVATION_ACTIVATE)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, handleFormPost.getEntityKey().getModelId())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
return handleFormPost;
|
||||
}
|
||||
|
||||
/** process a form post by first resetting all field validation errors (if there are some)
|
||||
* then collecting all input data from the form by form-binding to a either a JSON string in
|
||||
* HTTP PUT case or to an form-URL-encoded string on HTTP POST case. And PUT or POST the data
|
||||
* to the webservice by using the defined RestCall and return the response result of the RestCall.
|
||||
*
|
||||
* @return the response result of the post (or put) RestCall */
|
||||
public Result<T> doAPIPost() {
|
||||
// reset all errors that may still be displayed
|
||||
this.form.process(
|
||||
Utils.truePredicate(),
|
||||
fieldAccessor -> fieldAccessor.resetError());
|
||||
|
||||
// post
|
||||
return this.post
|
||||
.newBuilder()
|
||||
.withFormBinding(this.form)
|
||||
.call();
|
||||
}
|
||||
|
||||
/** Uses the result of a form post to either create and publish a new Action to
|
||||
* go to the read-only-view of the specified form to indicate a successful form post
|
||||
* or stay within the edit-mode of the form and indicate errors or field validation messages
|
||||
* to the user on error case.
|
||||
*
|
||||
* @param postResult The form post result
|
||||
* @param action the action that was applied with the form post
|
||||
* @return the new Action that was used to stay on page or go the read-only-view of the form */
|
||||
public PageAction handleFormPost(final Result<T> postResult, final PageAction action) {
|
||||
return postResult
|
||||
.map(result -> {
|
||||
|
||||
PageAction resultAction = this.pageService.pageActionBuilder(action.pageContext())
|
||||
.newAction(action.definition)
|
||||
.create();
|
||||
if (resultAction.getEntityKey() == null) {
|
||||
resultAction = resultAction.withEntityKey(result.getEntityKey());
|
||||
}
|
||||
|
||||
return resultAction;
|
||||
})
|
||||
.onError(this::handleError)
|
||||
.getOrThrow(FormPostException::new);
|
||||
}
|
||||
|
||||
public boolean handleError(final Exception error) {
|
||||
if (error instanceof RestCallError) {
|
||||
((RestCallError) error)
|
||||
.getErrorMessages()
|
||||
.stream()
|
||||
.filter(APIMessage.ErrorMessage.FIELD_VALIDATION::isOf)
|
||||
.map(FieldValidationError::new)
|
||||
.forEach(fve -> this.form.process(
|
||||
name -> name.equals(fve.fieldName),
|
||||
fieldAccessor -> showValidationError(fieldAccessor, fve)));
|
||||
return true;
|
||||
} else {
|
||||
log.error("Unexpected error while trying to post form: {}", error.getMessage());
|
||||
final EntityType resultType = this.post.getEntityType();
|
||||
if (resultType != null) {
|
||||
this.pageContext.notifySaveError(resultType, error);
|
||||
} else {
|
||||
this.pageContext.notifyError(
|
||||
new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, Constants.EMPTY_NOTE),
|
||||
error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAnyError() {
|
||||
return this.form.hasAnyError();
|
||||
}
|
||||
|
||||
private final void showValidationError(
|
||||
final FormFieldAccessor fieldAccessor,
|
||||
final FieldValidationError valError) {
|
||||
|
||||
fieldAccessor.setError(this.i18nSupport.getText(new LocTextKey(
|
||||
FIELD_VALIDATION_LOCTEXT_PREFIX + valError.errorType,
|
||||
(Object[]) valError.getAttributes())));
|
||||
}
|
||||
|
||||
public FormHandle<T> process(
|
||||
final Predicate<String> nameFilter,
|
||||
final Consumer<FormFieldAccessor> processor) {
|
||||
|
||||
this.form.process(nameFilter, processor);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,150 +1,150 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.form;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.browser.Browser;
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
public final class TextFieldBuilder extends FieldBuilder<String> {
|
||||
|
||||
private static final String HTML_TEXT_BLOCK_START =
|
||||
"<span style=\"font: 12px Arial, Helvetica, sans-serif;color: #4a4a4a;\">";
|
||||
private static final String HTML_TEXT_BLOCK_END = "</span>";
|
||||
|
||||
boolean isPassword = false;
|
||||
boolean isNumber = false;
|
||||
Consumer<String> numberCheck = null;
|
||||
boolean isArea = false;
|
||||
int areaMinHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
|
||||
boolean isColorbox = false;
|
||||
boolean isHTML = false;
|
||||
|
||||
TextFieldBuilder(final String name, final LocTextKey label, final String value) {
|
||||
super(name, label, value);
|
||||
}
|
||||
|
||||
public TextFieldBuilder asPasswordField() {
|
||||
this.isPassword = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asNumber() {
|
||||
this.isNumber = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asNumber(final Consumer<String> numberCheck) {
|
||||
this.isNumber = true;
|
||||
this.numberCheck = numberCheck;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asArea(final int minHeight) {
|
||||
this.areaMinHeight = minHeight;
|
||||
return asArea();
|
||||
}
|
||||
|
||||
public TextFieldBuilder asArea() {
|
||||
this.isArea = true;
|
||||
this.titleValign = SWT.CENTER;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asHTML() {
|
||||
this.isHTML = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FieldBuilder<?> asHTML(final boolean html) {
|
||||
this.isHTML = html;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asColorbox() {
|
||||
this.isColorbox = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final boolean readonly = builder.readonly || this.readonly;
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
|
||||
if (readonly && this.isHTML) {
|
||||
final Browser browser = new Browser(fieldGrid, SWT.NONE);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
|
||||
gridData.minimumHeight = this.areaMinHeight;
|
||||
browser.setBackground(new Color(builder.formParent.getDisplay(), new RGB(250, 250, 250)));
|
||||
browser.setLayoutData(gridData);
|
||||
if (StringUtils.isNoneBlank(this.value)) {
|
||||
browser.setText(createHTMLText(this.value));
|
||||
} else if (readonly) {
|
||||
browser.setText(Constants.EMPTY_NOTE);
|
||||
}
|
||||
builder.form.putReadonlyField(this.name, titleLabel, browser);
|
||||
return;
|
||||
}
|
||||
|
||||
final Text textInput = (this.isNumber)
|
||||
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly)
|
||||
: (this.isArea)
|
||||
? builder.widgetFactory.textAreaInput(fieldGrid, readonly)
|
||||
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly);
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
|
||||
if (this.isArea) {
|
||||
gridData.minimumHeight = this.areaMinHeight;
|
||||
} else if (this.isColorbox) {
|
||||
gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
|
||||
textInput.setData(RWT.CUSTOM_VARIANT, "colorbox");
|
||||
}
|
||||
textInput.setLayoutData(gridData);
|
||||
if (StringUtils.isNoneBlank(this.value)) {
|
||||
textInput.setText(this.value);
|
||||
} else if (readonly) {
|
||||
textInput.setText(Constants.EMPTY_NOTE);
|
||||
}
|
||||
|
||||
if (readonly) {
|
||||
textInput.setEditable(false);
|
||||
builder.form.putReadonlyField(this.name, titleLabel, textInput);
|
||||
} else {
|
||||
final Label errorLabel = createErrorLabel(fieldGrid);
|
||||
builder.form.putField(this.name, titleLabel, textInput, errorLabel);
|
||||
builder.setFieldVisible(this.visible, this.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String createHTMLText(final String text) {
|
||||
return HTML_TEXT_BLOCK_START
|
||||
+ text
|
||||
.replace("<a", "<span")
|
||||
.replace("</a", "</span")
|
||||
.replace("<A", "<span")
|
||||
.replace("</A", "</span")
|
||||
+ HTML_TEXT_BLOCK_END;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.form;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.browser.Browser;
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
public final class TextFieldBuilder extends FieldBuilder<String> {
|
||||
|
||||
private static final String HTML_TEXT_BLOCK_START =
|
||||
"<span style=\"font: 12px Arial, Helvetica, sans-serif;color: #4a4a4a;\">";
|
||||
private static final String HTML_TEXT_BLOCK_END = "</span>";
|
||||
|
||||
boolean isPassword = false;
|
||||
boolean isNumber = false;
|
||||
Consumer<String> numberCheck = null;
|
||||
boolean isArea = false;
|
||||
int areaMinHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
|
||||
boolean isColorBox = false;
|
||||
boolean isHTML = false;
|
||||
|
||||
TextFieldBuilder(final String name, final LocTextKey label, final String value) {
|
||||
super(name, label, value);
|
||||
}
|
||||
|
||||
public TextFieldBuilder asPasswordField() {
|
||||
this.isPassword = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asNumber() {
|
||||
this.isNumber = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asNumber(final Consumer<String> numberCheck) {
|
||||
this.isNumber = true;
|
||||
this.numberCheck = numberCheck;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asArea(final int minHeight) {
|
||||
this.areaMinHeight = minHeight;
|
||||
return asArea();
|
||||
}
|
||||
|
||||
public TextFieldBuilder asArea() {
|
||||
this.isArea = true;
|
||||
this.titleValign = SWT.CENTER;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asHTML() {
|
||||
this.isHTML = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FieldBuilder<?> asHTML(final boolean html) {
|
||||
this.isHTML = html;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asColorBox() {
|
||||
this.isColorBox = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final boolean readonly = builder.readonly || this.readonly;
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
|
||||
if (readonly && this.isHTML) {
|
||||
final Browser browser = new Browser(fieldGrid, SWT.NONE);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
|
||||
gridData.minimumHeight = this.areaMinHeight;
|
||||
browser.setBackground(new Color(builder.formParent.getDisplay(), new RGB(250, 250, 250)));
|
||||
browser.setLayoutData(gridData);
|
||||
if (StringUtils.isNoneBlank(this.value)) {
|
||||
browser.setText(createHTMLText(this.value));
|
||||
} else if (readonly) {
|
||||
browser.setText(Constants.EMPTY_NOTE);
|
||||
}
|
||||
builder.form.putReadonlyField(this.name, titleLabel, browser);
|
||||
return;
|
||||
}
|
||||
|
||||
final Text textInput = (this.isNumber)
|
||||
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly)
|
||||
: (this.isArea)
|
||||
? builder.widgetFactory.textAreaInput(fieldGrid, readonly)
|
||||
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly);
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
|
||||
if (this.isArea) {
|
||||
gridData.minimumHeight = this.areaMinHeight;
|
||||
} else if (this.isColorBox) {
|
||||
gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
|
||||
textInput.setData(RWT.CUSTOM_VARIANT, "colorbox");
|
||||
}
|
||||
textInput.setLayoutData(gridData);
|
||||
if (StringUtils.isNoneBlank(this.value)) {
|
||||
textInput.setText(this.value);
|
||||
} else if (readonly) {
|
||||
textInput.setText(Constants.EMPTY_NOTE);
|
||||
}
|
||||
|
||||
if (readonly) {
|
||||
textInput.setEditable(false);
|
||||
builder.form.putReadonlyField(this.name, titleLabel, textInput);
|
||||
} else {
|
||||
final Label errorLabel = createErrorLabel(fieldGrid);
|
||||
builder.form.putField(this.name, titleLabel, textInput, errorLabel);
|
||||
builder.setFieldVisible(this.visible, this.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String createHTMLText(final String text) {
|
||||
return HTML_TEXT_BLOCK_START
|
||||
+ text
|
||||
.replace("<a", "<span")
|
||||
.replace("</a", "</span")
|
||||
.replace("<A", "<span")
|
||||
.replace("</A", "</span")
|
||||
+ HTML_TEXT_BLOCK_END;
|
||||
}
|
||||
|
||||
}
|
|
@ -164,8 +164,7 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> lmsTypeResources() {
|
||||
return Arrays.asList(LmsType.values())
|
||||
.stream()
|
||||
return Arrays.stream(LmsType.values())
|
||||
.filter(lmsType -> lmsType != LmsType.MOCKUP || this.mock_lms_enabled)
|
||||
.map(lmsType -> new Tuple<>(
|
||||
lmsType.name(),
|
||||
|
@ -175,8 +174,7 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> clientEventTypeResources() {
|
||||
return Arrays.asList(EventType.values())
|
||||
.stream()
|
||||
return Arrays.stream(EventType.values())
|
||||
.filter(eventType -> !CLIENT_EVENT_TYPE_EXCLUDE_MAP.contains(eventType))
|
||||
.map(eventType -> new Tuple<>(
|
||||
eventType.name(),
|
||||
|
@ -200,8 +198,7 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> indicatorTypeResources() {
|
||||
return Arrays.asList(IndicatorType.values())
|
||||
.stream()
|
||||
return Arrays.stream(IndicatorType.values())
|
||||
.map(type -> new Tuple3<>(
|
||||
type.name(),
|
||||
this.i18nSupport.getText(EXAM_INDICATOR_TYPE_PREFIX + type.name(), type.name()),
|
||||
|
@ -295,8 +292,7 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> entityTypeResources() {
|
||||
return Arrays.asList(EntityType.values())
|
||||
.stream()
|
||||
return Arrays.stream(EntityType.values())
|
||||
.filter(type -> !ENTITY_TYPE_EXCLUDE_MAP.contains(type))
|
||||
.map(type -> new Tuple<>(type.name(), getEntityTypeName(type)))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
|
@ -319,8 +315,7 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> userActivityTypeResources() {
|
||||
return Arrays.asList(UserLogActivityType.values())
|
||||
.stream()
|
||||
return Arrays.stream(UserLogActivityType.values())
|
||||
.map(type -> new Tuple<>(type.name(), getUserActivityTypeName(type)))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
|
@ -363,16 +358,13 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> examTypeResources() {
|
||||
return Arrays.asList(ExamType.values())
|
||||
.stream()
|
||||
.filter(type -> type != ExamType.UNDEFINED)
|
||||
return Arrays.stream(ExamType.values())
|
||||
.map(type -> new Tuple3<>(
|
||||
type.name(),
|
||||
this.i18nSupport.getText(EXAM_TYPE_PREFIX + type.name()),
|
||||
Utils.formatLineBreaks(this.i18nSupport.getText(
|
||||
this.i18nSupport.getText(EXAM_TYPE_PREFIX + type.name()) + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))
|
||||
))
|
||||
EXAM_INDICATOR_TYPE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
@ -382,8 +374,7 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> examConfigStatusResources(final boolean isAttachedToExam) {
|
||||
return Arrays.asList(ConfigurationStatus.values())
|
||||
.stream()
|
||||
return Arrays.stream(ConfigurationStatus.values())
|
||||
.filter(status -> {
|
||||
if (isAttachedToExam) {
|
||||
return status != ConfigurationStatus.READY_TO_USE;
|
||||
|
@ -579,8 +570,7 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> getAttributeTypeResources() {
|
||||
return Arrays.asList(AttributeType.values())
|
||||
.stream()
|
||||
return Arrays.stream(AttributeType.values())
|
||||
.filter(type -> !ATTRIBUTE_TYPES_NOT_DISPLAYED.contains(type))
|
||||
.map(type -> new Tuple<>(getAttributeTypeFilterName(type), getAttributeTypeName(type)))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
|
@ -629,21 +619,25 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> sebRestrictionWhiteListResources() {
|
||||
return Arrays.asList(WhiteListPath.values())
|
||||
.stream()
|
||||
.map(type -> new Tuple<>(
|
||||
return Arrays.stream(WhiteListPath.values())
|
||||
.map(type -> new Tuple3<>(
|
||||
type.key,
|
||||
this.i18nSupport.getText(SEB_RESTRICTION_WHITE_LIST_PREFIX + type.name(), type.key)))
|
||||
this.i18nSupport.getText(SEB_RESTRICTION_WHITE_LIST_PREFIX + type.name(), type.key),
|
||||
Utils.formatLineBreaks(this.i18nSupport.getText(
|
||||
SEB_RESTRICTION_WHITE_LIST_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Tuple<String>> sebRestrictionPermissionResources() {
|
||||
return Arrays.asList(PermissionComponent.values())
|
||||
.stream()
|
||||
.map(type -> new Tuple<>(
|
||||
return Arrays.stream(PermissionComponent.values())
|
||||
.map(type -> new Tuple3<>(
|
||||
type.key,
|
||||
this.i18nSupport.getText(SEB_RESTRICTION_PERMISSIONS_PREFIX + type.name(), type.key)))
|
||||
this.i18nSupport.getText(SEB_RESTRICTION_PERMISSIONS_PREFIX + type.name(), type.key),
|
||||
Utils.formatLineBreaks(this.i18nSupport.getText(
|
||||
SEB_RESTRICTION_PERMISSIONS_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,384 +1,396 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.service.page.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Activatable;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.MultiPageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageStateDefinition.Type;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@GuiProfile
|
||||
public class PageServiceImpl implements PageService {
|
||||
|
||||
private static final LocTextKey CONFIRM_DEACTIVATION_NO_DEP_KEY =
|
||||
new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies");
|
||||
|
||||
private static final String CONFIRM_DEACTIVATION_KEY = "sebserver.dialog.confirm.deactivation";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PageServiceImpl.class);
|
||||
|
||||
private static final LocTextKey MSG_GO_AWAY_FROM_EDIT =
|
||||
new LocTextKey("sebserver.overall.action.goAwayFromEditPageConfirm");
|
||||
|
||||
private static final String ATTR_PAGE_STATE = "PAGE_STATE";
|
||||
private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator();
|
||||
|
||||
private final JSONMapper jsonMapper;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final PolyglotPageService polyglotPageService;
|
||||
private final ResourceService resourceService;
|
||||
private final CurrentUser currentUser;
|
||||
|
||||
public PageServiceImpl(
|
||||
final JSONMapper jsonMapper,
|
||||
final WidgetFactory widgetFactory,
|
||||
final PolyglotPageService polyglotPageService,
|
||||
final ResourceService resourceService,
|
||||
final CurrentUser currentUser) {
|
||||
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.widgetFactory = widgetFactory;
|
||||
this.polyglotPageService = polyglotPageService;
|
||||
this.resourceService = resourceService;
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WidgetFactory getWidgetFactory() {
|
||||
return this.widgetFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolyglotPageService getPolyglotPageService() {
|
||||
return this.polyglotPageService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationContextHolder getAuthorizationContextHolder() {
|
||||
return this.currentUser.getAuthorizationContextHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18nSupport getI18nSupport() {
|
||||
return this.widgetFactory.getI18nSupport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceService getResourceService() {
|
||||
return this.resourceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONMapper getJSONMapper() {
|
||||
return this.jsonMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestService getRestService() {
|
||||
if (this.resourceService == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.resourceService.getRestService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurrentUser getCurrentUser() {
|
||||
return this.currentUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageState getCurrentState() {
|
||||
try {
|
||||
|
||||
final HttpSession httpSession = RWT
|
||||
.getUISession()
|
||||
.getHttpSession();
|
||||
|
||||
return (PageState) httpSession.getAttribute(ATTR_PAGE_STATE);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to get current PageState: ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PageEvent> void firePageEvent(final T event, final PageContext pageContext) {
|
||||
final Class<? extends PageEvent> typeClass = event.getClass();
|
||||
final List<PageEventListener<T>> listeners = new ArrayList<>();
|
||||
ComposerService.traversePageTree(
|
||||
pageContext.getRoot(),
|
||||
c -> {
|
||||
final PageEventListener<?> listener =
|
||||
(PageEventListener<?>) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY);
|
||||
return listener != null && listener.match(typeClass);
|
||||
},
|
||||
c -> listeners.add(((PageEventListener<T>) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY))));
|
||||
|
||||
if (listeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.stream()
|
||||
.sorted(LIST_COMPARATOR)
|
||||
.forEach(listener -> {
|
||||
try {
|
||||
listener.notify(event);
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while notify PageEventListener: ", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePageAction(final PageAction pageAction, final Consumer<Result<PageAction>> callback) {
|
||||
final PageState currentState = getCurrentState();
|
||||
if (!pageAction.ignoreMoveAwayFromEdit && currentState != null && currentState.type() == Type.FORM_EDIT) {
|
||||
pageAction.pageContext().applyConfirmDialog(
|
||||
MSG_GO_AWAY_FROM_EDIT,
|
||||
confirm -> {
|
||||
if (confirm) {
|
||||
exec(pageAction, callback);
|
||||
} else {
|
||||
callback.accept(Result.ofRuntimeError("Confirm denied"));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
exec(pageAction, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final Set<? extends T> entities) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
return () -> {
|
||||
if (entities == null || entities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final int dependencies = entities.stream()
|
||||
.flatMap(entity -> {
|
||||
final RestCall<Set<EntityKey>>.RestCallBuilder builder =
|
||||
restService.<Set<EntityKey>> getBuilder(
|
||||
entity.entityType(),
|
||||
CallType.GET_DEPENDENCIES);
|
||||
|
||||
return builder
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId()))
|
||||
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name())
|
||||
.call()
|
||||
.getOrThrow().stream();
|
||||
})
|
||||
.collect(Collectors.toList())
|
||||
.size();
|
||||
if (dependencies > 0) {
|
||||
return new LocTextKey(CONFIRM_DEACTIVATION_KEY, String.valueOf(dependencies));
|
||||
} else {
|
||||
return CONFIRM_DEACTIVATION_NO_DEP_KEY;
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to get dependencyies. Error: {}", e.getMessage());
|
||||
return new LocTextKey(CONFIRM_DEACTIVATION_KEY, "");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Entity & Activatable> Function<PageAction, PageAction> activationToggleActionFunction(
|
||||
final EntityTable<T> table,
|
||||
final LocTextKey noSelectionText) {
|
||||
|
||||
return action -> {
|
||||
final Set<T> selectedROWData = table.getSelectedROWData();
|
||||
if (selectedROWData == null || selectedROWData.isEmpty()) {
|
||||
throw new PageMessageException(noSelectionText);
|
||||
}
|
||||
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final EntityType entityType = table.getEntityType();
|
||||
|
||||
final Collection<Exception> errors = new ArrayList<>();
|
||||
for (final T entity : selectedROWData) {
|
||||
if (entity.isActive()) {
|
||||
restService.getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
|
||||
.call()
|
||||
.onError(errors::add);
|
||||
} else {
|
||||
restService.getBuilder(entityType, CallType.ACTIVATION_ACTIVATE)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
|
||||
.call()
|
||||
.onError(errors::add);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
final String entityTypeName = this.resourceService.getEntityTypeName(entityType);
|
||||
throw new MultiPageMessageException(
|
||||
new LocTextKey(PageContext.GENERIC_ACTIVATE_ERROR_TEXT_KEY, entityTypeName),
|
||||
errors);
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private void exec(final PageAction pageAction, final Consumer<Result<PageAction>> callback) {
|
||||
pageAction.applyAction(result -> {
|
||||
if (!result.hasError()) {
|
||||
|
||||
final PageAction action = result.get();
|
||||
if (pageAction.fireActionEvent) {
|
||||
firePageEvent(new ActionEvent(action), action.pageContext());
|
||||
}
|
||||
|
||||
try {
|
||||
final HttpSession httpSession = RWT
|
||||
.getUISession()
|
||||
.getHttpSession();
|
||||
|
||||
if (action != null &&
|
||||
action.fireActionEvent &&
|
||||
action.definition != null &&
|
||||
action.definition.targetState != null) {
|
||||
|
||||
final PageState pageState = new PageState(action.definition.targetState, action);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Set session PageState: {} : {}", pageState, httpSession.getId());
|
||||
}
|
||||
httpSession.setAttribute(ATTR_PAGE_STATE, pageState);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to set current PageState: ", e);
|
||||
}
|
||||
}
|
||||
callback.accept(result);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishAction(final PageAction pageAction, final boolean active) {
|
||||
this.firePageEvent(new ActionPublishEvent(pageAction, active), pageAction.pageContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormBuilder formBuilder(final PageContext pageContext, final int rows) {
|
||||
return new FormBuilder(this, pageContext, rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Entity> TableBuilder<T> entityTableBuilder(
|
||||
final String name,
|
||||
final RestCall<Page<T>> apiCall) {
|
||||
|
||||
return new TableBuilder<>(name, this, apiCall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(final PageContext pageContext) {
|
||||
this.clearState();
|
||||
|
||||
try {
|
||||
final boolean logoutSuccessful = this.currentUser.logout();
|
||||
|
||||
if (!logoutSuccessful) {
|
||||
log.warn("Failed to logout. See logfiles for more information");
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.info("Cleanup logout failed: {}", e.getMessage());
|
||||
} finally {
|
||||
pageContext.forwardToLoginPage();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
try {
|
||||
|
||||
final HttpSession httpSession = RWT
|
||||
.getUISession()
|
||||
.getHttpSession();
|
||||
|
||||
log.debug("Clear session PageState: {}", httpSession.getId());
|
||||
httpSession.removeAttribute(ATTR_PAGE_STATE);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to clear current PageState: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ListenerComparator implements Comparator<PageEventListener<?>>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2571739214439340404L;
|
||||
|
||||
@Override
|
||||
public int compare(final PageEventListener<?> o1, final PageEventListener<?> o2) {
|
||||
final int x = o1.priority();
|
||||
final int y = o2.priority();
|
||||
return (x < y) ? -1 : ((x == y) ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.service.page.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Activatable;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.MultiPageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageStateDefinition.Type;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@GuiProfile
|
||||
public class PageServiceImpl implements PageService {
|
||||
|
||||
private static final LocTextKey CONFIRM_DEACTIVATION_NO_DEP_KEY =
|
||||
new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies");
|
||||
|
||||
private static final String CONFIRM_DEACTIVATION_KEY = "sebserver.dialog.confirm.deactivation";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PageServiceImpl.class);
|
||||
|
||||
private static final LocTextKey MSG_GO_AWAY_FROM_EDIT =
|
||||
new LocTextKey("sebserver.overall.action.goAwayFromEditPageConfirm");
|
||||
|
||||
private static final String ATTR_PAGE_STATE = "PAGE_STATE";
|
||||
private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator();
|
||||
|
||||
private final JSONMapper jsonMapper;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final PolyglotPageService polyglotPageService;
|
||||
private final ResourceService resourceService;
|
||||
private final CurrentUser currentUser;
|
||||
|
||||
public PageServiceImpl(
|
||||
final JSONMapper jsonMapper,
|
||||
final WidgetFactory widgetFactory,
|
||||
final PolyglotPageService polyglotPageService,
|
||||
final ResourceService resourceService,
|
||||
final CurrentUser currentUser) {
|
||||
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.widgetFactory = widgetFactory;
|
||||
this.polyglotPageService = polyglotPageService;
|
||||
this.resourceService = resourceService;
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WidgetFactory getWidgetFactory() {
|
||||
return this.widgetFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolyglotPageService getPolyglotPageService() {
|
||||
return this.polyglotPageService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationContextHolder getAuthorizationContextHolder() {
|
||||
return this.currentUser.getAuthorizationContextHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18nSupport getI18nSupport() {
|
||||
return this.widgetFactory.getI18nSupport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceService getResourceService() {
|
||||
return this.resourceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONMapper getJSONMapper() {
|
||||
return this.jsonMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestService getRestService() {
|
||||
if (this.resourceService == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.resourceService.getRestService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurrentUser getCurrentUser() {
|
||||
return this.currentUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageState getCurrentState() {
|
||||
try {
|
||||
|
||||
final HttpSession httpSession = RWT
|
||||
.getUISession()
|
||||
.getHttpSession();
|
||||
|
||||
return (PageState) httpSession.getAttribute(ATTR_PAGE_STATE);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to get current PageState: ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PageEvent> void firePageEvent(final T event, final PageContext pageContext) {
|
||||
final Class<? extends PageEvent> typeClass = event.getClass();
|
||||
final List<PageEventListener<T>> listeners = new ArrayList<>();
|
||||
ComposerService.traversePageTree(
|
||||
pageContext.getRoot(),
|
||||
c -> {
|
||||
final PageEventListener<?> listener =
|
||||
(PageEventListener<?>) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY);
|
||||
return listener != null && listener.match(typeClass);
|
||||
},
|
||||
c -> listeners.add(((PageEventListener<T>) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY))));
|
||||
|
||||
if (listeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.stream()
|
||||
.sorted(LIST_COMPARATOR)
|
||||
.forEach(listener -> {
|
||||
try {
|
||||
listener.notify(event);
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while notify PageEventListener: ", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePageAction(final PageAction pageAction, final Consumer<Result<PageAction>> callback) {
|
||||
final PageState currentState = getCurrentState();
|
||||
if (!pageAction.ignoreMoveAwayFromEdit && currentState != null && currentState.type() == Type.FORM_EDIT) {
|
||||
pageAction.pageContext().applyConfirmDialog(
|
||||
MSG_GO_AWAY_FROM_EDIT,
|
||||
confirm -> {
|
||||
if (confirm) {
|
||||
exec(pageAction, callback);
|
||||
} else {
|
||||
callback.accept(Result.ofRuntimeError("Confirm denied"));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
exec(pageAction, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final Set<? extends T> entities) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
return () -> {
|
||||
if (entities == null || entities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final int dependencies = (int) entities.stream()
|
||||
.flatMap(entity -> {
|
||||
final RestCall<Set<EntityKey>>.RestCallBuilder builder =
|
||||
restService.<Set<EntityKey>>getBuilder(
|
||||
entity.entityType(),
|
||||
CallType.GET_DEPENDENCIES);
|
||||
|
||||
return builder
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId()))
|
||||
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name())
|
||||
.call()
|
||||
.getOrThrow().stream();
|
||||
}).count();
|
||||
if (dependencies > 0) {
|
||||
return new LocTextKey(CONFIRM_DEACTIVATION_KEY, String.valueOf(dependencies));
|
||||
} else {
|
||||
return CONFIRM_DEACTIVATION_NO_DEP_KEY;
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to get dependencies. Error: {}", e.getMessage());
|
||||
return new LocTextKey(CONFIRM_DEACTIVATION_KEY, "");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Entity & Activatable> Function<PageAction, PageAction> activationToggleActionFunction(
|
||||
final EntityTable<T> table,
|
||||
final LocTextKey noSelectionText,
|
||||
Function<PageAction, PageAction> testBeforeActivation) {
|
||||
|
||||
return action -> {
|
||||
final Set<T> selectedROWData = table.getSelectedROWData();
|
||||
if (selectedROWData == null || selectedROWData.isEmpty()) {
|
||||
throw new PageMessageException(noSelectionText);
|
||||
}
|
||||
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final EntityType entityType = table.getEntityType();
|
||||
|
||||
final Collection<Exception> errors = new ArrayList<>();
|
||||
for (final T entity : selectedROWData) {
|
||||
|
||||
if (!entity.isActive()) {
|
||||
RestCall<T>.RestCallBuilder restCallBuilder = restService.<T>getBuilder(
|
||||
entityType,
|
||||
CallType.ACTIVATION_ACTIVATE)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entity.getModelId());
|
||||
if (testBeforeActivation != null) {
|
||||
try {
|
||||
action.withEntityKey(entity.getEntityKey());
|
||||
testBeforeActivation.apply(action);
|
||||
restCallBuilder
|
||||
.call()
|
||||
.onError(errors::add);
|
||||
} catch (Exception e) {
|
||||
errors.add(e);
|
||||
}
|
||||
} else {
|
||||
restCallBuilder
|
||||
.call()
|
||||
.onError(errors::add);
|
||||
}
|
||||
} else {
|
||||
restService.<T>getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
|
||||
.call()
|
||||
.onError(errors::add);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
final String entityTypeName = this.resourceService.getEntityTypeName(entityType);
|
||||
throw new MultiPageMessageException(
|
||||
new LocTextKey(PageContext.GENERIC_ACTIVATE_ERROR_TEXT_KEY, entityTypeName),
|
||||
errors);
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private void exec(final PageAction pageAction, final Consumer<Result<PageAction>> callback) {
|
||||
pageAction.applyAction(result -> {
|
||||
if (!result.hasError()) {
|
||||
|
||||
final PageAction action = result.get();
|
||||
if (pageAction.fireActionEvent) {
|
||||
firePageEvent(new ActionEvent(action), action.pageContext());
|
||||
}
|
||||
|
||||
try {
|
||||
final HttpSession httpSession = RWT
|
||||
.getUISession()
|
||||
.getHttpSession();
|
||||
|
||||
if (action != null &&
|
||||
action.fireActionEvent &&
|
||||
action.definition != null &&
|
||||
action.definition.targetState != null) {
|
||||
|
||||
final PageState pageState = new PageState(action.definition.targetState, action);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Set session PageState: {} : {}", pageState, httpSession.getId());
|
||||
}
|
||||
httpSession.setAttribute(ATTR_PAGE_STATE, pageState);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to set current PageState: ", e);
|
||||
}
|
||||
}
|
||||
callback.accept(result);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishAction(final PageAction pageAction, final boolean active) {
|
||||
this.firePageEvent(new ActionPublishEvent(pageAction, active), pageAction.pageContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormBuilder formBuilder(final PageContext pageContext, final int rows) {
|
||||
return new FormBuilder(this, pageContext, rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Entity> TableBuilder<T> entityTableBuilder(
|
||||
final String name,
|
||||
final RestCall<Page<T>> apiCall) {
|
||||
|
||||
return new TableBuilder<>(name, this, apiCall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(final PageContext pageContext) {
|
||||
this.clearState();
|
||||
|
||||
try {
|
||||
final boolean logoutSuccessful = this.currentUser.logout();
|
||||
|
||||
if (!logoutSuccessful) {
|
||||
log.warn("Failed to logout. See logfiles for more information");
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.info("Cleanup logout failed: {}", e.getMessage());
|
||||
} finally {
|
||||
pageContext.forwardToLoginPage();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
try {
|
||||
|
||||
final HttpSession httpSession = RWT
|
||||
.getUISession()
|
||||
.getHttpSession();
|
||||
|
||||
log.debug("Clear session PageState: {}", httpSession.getId());
|
||||
httpSession.removeAttribute(ATTR_PAGE_STATE);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to clear current PageState: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ListenerComparator implements Comparator<PageEventListener<?>>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2571739214439340404L;
|
||||
|
||||
@Override
|
||||
public int compare(final PageEventListener<?> o1, final PageEventListener<?> o2) {
|
||||
return Integer.compare(o1.priority(), o2.priority());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,195 +1,191 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.service.session;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.PageService;
|
||||
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.session.IndicatorData.ThresholdColor;
|
||||
|
||||
public class ClientConnectionDetails {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class);
|
||||
|
||||
private final static LocTextKey EXAM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.examname");
|
||||
private final static LocTextKey CONNECTION_ID_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.id");
|
||||
private final static LocTextKey CONNECTION_ADDRESS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.address");
|
||||
private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.status");
|
||||
|
||||
private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final Exam exam;
|
||||
private final EnumMap<IndicatorType, IndicatorData> indicatorMapping;
|
||||
private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder;
|
||||
private final FormHandle<?> formhandle;
|
||||
private final ColorData colorData;
|
||||
|
||||
private ClientConnectionData connectionData = null;
|
||||
private boolean statusChanged = true;
|
||||
|
||||
public ClientConnectionDetails(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final Exam exam,
|
||||
final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder,
|
||||
final Collection<Indicator> indicators) {
|
||||
|
||||
final Display display = pageContext.getRoot().getDisplay();
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.exam = exam;
|
||||
this.restCallBuilder = restCallBuilder;
|
||||
this.colorData = new ColorData(display);
|
||||
this.indicatorMapping = IndicatorData.createFormIndicators(
|
||||
indicators,
|
||||
display,
|
||||
this.colorData,
|
||||
NUMBER_OF_NONE_INDICATOR_ROWS);
|
||||
|
||||
final FormBuilder formBuilder = this.pageService.formBuilder(pageContext)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
EXAM_NAME_TEXT_KEY,
|
||||
this.exam.getName()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
CONNECTION_ID_TEXT_KEY,
|
||||
Constants.EMPTY_NOTE))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
|
||||
CONNECTION_ADDRESS_TEXT_KEY,
|
||||
Constants.EMPTY_NOTE))
|
||||
.withDefaultSpanInput(3)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
CONNECTION_STATUS_TEXT_KEY,
|
||||
Constants.EMPTY_NOTE)
|
||||
.asColorbox())
|
||||
.addEmptyCell();
|
||||
|
||||
this.indicatorMapping
|
||||
.values()
|
||||
.stream()
|
||||
.forEach(indData -> {
|
||||
formBuilder.addField(FormBuilder.text(
|
||||
indData.indicator.name,
|
||||
new LocTextKey(indData.indicator.name),
|
||||
Constants.EMPTY_NOTE)
|
||||
.asColorbox()
|
||||
.withDefaultLabel(indData.indicator.name))
|
||||
.addEmptyCell();
|
||||
});
|
||||
|
||||
this.formhandle = formBuilder.build();
|
||||
}
|
||||
|
||||
public void updateData(final ServerPushContext context) {
|
||||
final ClientConnectionData connectionData = this.restCallBuilder
|
||||
.call()
|
||||
.get(error -> {
|
||||
log.error("Unexpected error while trying to get current client connection data: ", error);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (this.connectionData != null && connectionData != null) {
|
||||
this.statusChanged =
|
||||
this.connectionData.clientConnection.status != connectionData.clientConnection.status ||
|
||||
this.connectionData.missingPing != connectionData.missingPing;
|
||||
}
|
||||
this.connectionData = connectionData;
|
||||
}
|
||||
|
||||
public void updateGUI(final ServerPushContext context) {
|
||||
if (this.connectionData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Form form = this.formhandle.getForm();
|
||||
form.setFieldValue(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
this.connectionData.clientConnection.userSessionId);
|
||||
|
||||
form.setFieldValue(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
|
||||
this.connectionData.clientConnection.clientAddress);
|
||||
|
||||
if (this.statusChanged) {
|
||||
// update status
|
||||
form.setFieldValue(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
this.resourceService.localizedClientConnectionStatusName(this.connectionData));
|
||||
final Color statusColor = this.colorData.getStatusColor(this.connectionData);
|
||||
final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
|
||||
form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);
|
||||
form.setFieldTextColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusTextColor);
|
||||
}
|
||||
|
||||
// update indicators
|
||||
this.connectionData.getIndicatorValues()
|
||||
.stream()
|
||||
.forEach(indValue -> {
|
||||
final IndicatorData indData = this.indicatorMapping.get(indValue.getType());
|
||||
final double value = indValue.getValue();
|
||||
final String displayValue = IndicatorValue.getDisplayValue(indValue);
|
||||
|
||||
if (!this.connectionData.clientConnection.status.establishedStatus) {
|
||||
|
||||
form.setFieldValue(
|
||||
indData.indicator.name,
|
||||
(indData.indicator.type.showOnlyInActiveState)
|
||||
? Constants.EMPTY_NOTE
|
||||
: displayValue);
|
||||
form.setFieldColor(indData.indicator.name, indData.defaultColor);
|
||||
form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
|
||||
} else {
|
||||
form.setFieldValue(indData.indicator.name, displayValue);
|
||||
final int weight = IndicatorData.getWeight(indData, value);
|
||||
if (weight >= 0 && weight < indData.thresholdColor.length) {
|
||||
final ThresholdColor thresholdColor = indData.thresholdColor[weight];
|
||||
form.setFieldColor(indData.indicator.name, thresholdColor.color);
|
||||
form.setFieldTextColor(indData.indicator.name, thresholdColor.textColor);
|
||||
} else {
|
||||
form.setFieldColor(indData.indicator.name, indData.defaultColor);
|
||||
form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.service.session;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.PageService;
|
||||
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.session.IndicatorData.ThresholdColor;
|
||||
|
||||
public class ClientConnectionDetails {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class);
|
||||
|
||||
private final static LocTextKey EXAM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.form.exam");
|
||||
private final static LocTextKey CONNECTION_ID_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.form.id");
|
||||
private final static LocTextKey CONNECTION_ADDRESS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.form.address");
|
||||
private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.form.status");
|
||||
|
||||
private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final Exam exam;
|
||||
private final EnumMap<IndicatorType, IndicatorData> indicatorMapping;
|
||||
private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder;
|
||||
private final FormHandle<?> formhandle;
|
||||
private final ColorData colorData;
|
||||
|
||||
private ClientConnectionData connectionData = null;
|
||||
private boolean statusChanged = true;
|
||||
|
||||
public ClientConnectionDetails(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final Exam exam,
|
||||
final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder,
|
||||
final Collection<Indicator> indicators) {
|
||||
|
||||
final Display display = pageContext.getRoot().getDisplay();
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.exam = exam;
|
||||
this.restCallBuilder = restCallBuilder;
|
||||
this.colorData = new ColorData(display);
|
||||
this.indicatorMapping = IndicatorData.createFormIndicators(
|
||||
indicators,
|
||||
display,
|
||||
this.colorData,
|
||||
NUMBER_OF_NONE_INDICATOR_ROWS);
|
||||
|
||||
final FormBuilder formBuilder = this.pageService.formBuilder(pageContext)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
EXAM_NAME_TEXT_KEY,
|
||||
this.exam.getName()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
CONNECTION_ID_TEXT_KEY,
|
||||
Constants.EMPTY_NOTE))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
|
||||
CONNECTION_ADDRESS_TEXT_KEY,
|
||||
Constants.EMPTY_NOTE))
|
||||
.withDefaultSpanInput(3)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
CONNECTION_STATUS_TEXT_KEY,
|
||||
Constants.EMPTY_NOTE)
|
||||
.asColorBox())
|
||||
.addEmptyCell();
|
||||
|
||||
this.indicatorMapping
|
||||
.values()
|
||||
.forEach(indData -> formBuilder.addField(FormBuilder.text(
|
||||
indData.indicator.name,
|
||||
new LocTextKey(indData.indicator.name),
|
||||
Constants.EMPTY_NOTE)
|
||||
.asColorBox()
|
||||
.withDefaultLabel(indData.indicator.name))
|
||||
.addEmptyCell());
|
||||
|
||||
this.formhandle = formBuilder.build();
|
||||
}
|
||||
|
||||
public void updateData() {
|
||||
final ClientConnectionData connectionData = this.restCallBuilder
|
||||
.call()
|
||||
.get(error -> {
|
||||
log.error("Unexpected error while trying to get current client connection data: ", error);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (this.connectionData != null && connectionData != null) {
|
||||
this.statusChanged =
|
||||
this.connectionData.clientConnection.status != connectionData.clientConnection.status ||
|
||||
this.connectionData.missingPing != connectionData.missingPing;
|
||||
}
|
||||
this.connectionData = connectionData;
|
||||
}
|
||||
|
||||
public void updateGUI() {
|
||||
if (this.connectionData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Form form = this.formhandle.getForm();
|
||||
form.setFieldValue(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
this.connectionData.clientConnection.userSessionId);
|
||||
|
||||
form.setFieldValue(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
|
||||
this.connectionData.clientConnection.clientAddress);
|
||||
|
||||
if (this.statusChanged) {
|
||||
// update status
|
||||
form.setFieldValue(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
this.resourceService.localizedClientConnectionStatusName(this.connectionData));
|
||||
final Color statusColor = this.colorData.getStatusColor(this.connectionData);
|
||||
final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
|
||||
form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);
|
||||
form.setFieldTextColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusTextColor);
|
||||
}
|
||||
|
||||
// update indicators
|
||||
this.connectionData.getIndicatorValues()
|
||||
.forEach(indValue -> {
|
||||
final IndicatorData indData = this.indicatorMapping.get(indValue.getType());
|
||||
final double value = indValue.getValue();
|
||||
final String displayValue = IndicatorValue.getDisplayValue(indValue);
|
||||
|
||||
if (!this.connectionData.clientConnection.status.establishedStatus) {
|
||||
|
||||
form.setFieldValue(
|
||||
indData.indicator.name,
|
||||
(indData.indicator.type.showOnlyInActiveState)
|
||||
? Constants.EMPTY_NOTE
|
||||
: displayValue);
|
||||
form.setFieldColor(indData.indicator.name, indData.defaultColor);
|
||||
form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
|
||||
} else {
|
||||
form.setFieldValue(indData.indicator.name, displayValue);
|
||||
final int weight = IndicatorData.getWeight(indData, value);
|
||||
if (weight >= 0 && weight < indData.thresholdColor.length) {
|
||||
final ThresholdColor thresholdColor = indData.thresholdColor[weight];
|
||||
form.setFieldColor(indData.indicator.name, thresholdColor.color);
|
||||
form.setFieldTextColor(indData.indicator.name, thresholdColor.textColor);
|
||||
} else {
|
||||
form.setFieldColor(indData.indicator.name, indData.defaultColor);
|
||||
form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -104,7 +104,6 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
|
|||
}
|
||||
|
||||
Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR))
|
||||
.stream()
|
||||
.forEach(key -> {
|
||||
final Button button = this.checkboxes.get(key);
|
||||
if (button != null) {
|
||||
|
@ -120,7 +119,7 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
|
|||
this.checkboxes
|
||||
.values()
|
||||
.stream()
|
||||
.filter(button -> button.getSelection())
|
||||
.filter(Button::getSelection)
|
||||
.map(button -> (String) button.getData(OPTION_VALUE))
|
||||
.collect(Collectors.toList()).toArray());
|
||||
}
|
||||
|
@ -129,7 +128,6 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
|
|||
public void clear() {
|
||||
this.checkboxes
|
||||
.values()
|
||||
.stream()
|
||||
.forEach(button -> button.setSelection(false));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,242 +1,249 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gui.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
||||
|
||||
public final class ThresholdList extends Composite {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ThresholdList.class);
|
||||
private static final long serialVersionUID = -2305091471607040280L;
|
||||
private static final int ACTION_COLUMN_WIDTH = 20;
|
||||
|
||||
private static final String COLOR_SELECTION_TEXT_KEY = "sebserver.exam.indicator.thresholds.select.color";
|
||||
private static final LocTextKey VALUE_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.value");
|
||||
private static final LocTextKey COLOR_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.color");
|
||||
private static final LocTextKey ADD_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.add");
|
||||
private static final LocTextKey REMOVE_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.remove");
|
||||
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final Supplier<IndicatorType> indicatorTypeSupplier;
|
||||
private final List<Entry> thresholds = new ArrayList<>();
|
||||
|
||||
private final GridData valueCell;
|
||||
private final GridData colorCell;
|
||||
private final GridData actionCell;
|
||||
private final Composite updateAnchor;
|
||||
|
||||
ThresholdList(
|
||||
final Composite parent,
|
||||
final Composite updateAnchor,
|
||||
final WidgetFactory widgetFactory,
|
||||
final Supplier<IndicatorType> indicatorTypeSupplier) {
|
||||
|
||||
super(parent, SWT.NONE);
|
||||
this.indicatorTypeSupplier = indicatorTypeSupplier;
|
||||
this.updateAnchor = updateAnchor;
|
||||
this.widgetFactory = widgetFactory;
|
||||
super.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
final GridLayout gridLayout = new GridLayout(3, false);
|
||||
gridLayout.verticalSpacing = 1;
|
||||
gridLayout.marginLeft = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
gridLayout.horizontalSpacing = 0;
|
||||
setLayout(gridLayout);
|
||||
|
||||
this.addListener(SWT.Resize, this::adaptColumnWidth);
|
||||
|
||||
final Label valueTitle = widgetFactory.labelLocalized(
|
||||
this,
|
||||
CustomVariant.TITLE_LABEL,
|
||||
VALUE_TEXT_KEY);
|
||||
this.valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
valueTitle.setLayoutData(this.valueCell);
|
||||
|
||||
final Label colorTitle = widgetFactory.labelLocalized(
|
||||
this,
|
||||
CustomVariant.TITLE_LABEL,
|
||||
COLOR_TEXT_KEY);
|
||||
this.colorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
colorTitle.setLayoutData(this.colorCell);
|
||||
|
||||
final Label imageButton = widgetFactory.imageButton(
|
||||
ImageIcon.ADD_BOX,
|
||||
this,
|
||||
ADD_TEXT_KEY,
|
||||
this::addThreshold);
|
||||
this.actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
|
||||
imageButton.setLayoutData(this.actionCell);
|
||||
}
|
||||
|
||||
public void setThresholds(final Collection<Threshold> thresholds) {
|
||||
clearList();
|
||||
if (thresholds != null) {
|
||||
thresholds
|
||||
.stream()
|
||||
.forEach(this::addThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Threshold> getThresholds() {
|
||||
removeInvalidListEntries();
|
||||
return this.thresholds
|
||||
.stream()
|
||||
.map(entry -> new Threshold(entry.getValue(), entry.getColor()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void removeInvalidListEntries() {
|
||||
this.thresholds
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue() == null || StringUtils.isBlank(entry.getColor()))
|
||||
.collect(Collectors.toList())
|
||||
.stream()
|
||||
.forEach(entry -> removeThreshold(entry));
|
||||
}
|
||||
|
||||
private void clearList() {
|
||||
this.thresholds.stream()
|
||||
.forEach(e -> e.dispose());
|
||||
this.thresholds.clear();
|
||||
}
|
||||
|
||||
private void addThreshold(final Event event) {
|
||||
addThreshold((Threshold) null);
|
||||
}
|
||||
|
||||
private void addThreshold(final Threshold threshold) {
|
||||
final Text valueInput = this.widgetFactory.numberInput(
|
||||
this, s -> {
|
||||
if (this.indicatorTypeSupplier.get().integerValue) {
|
||||
Integer.parseInt(s);
|
||||
} else {
|
||||
Double.parseDouble(s);
|
||||
}
|
||||
});
|
||||
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
valueInput.setLayoutData(valueCell);
|
||||
|
||||
final Selection selector = this.widgetFactory.selectionLocalized(
|
||||
Type.COLOR, this, null, null, null,
|
||||
COLOR_SELECTION_TEXT_KEY);
|
||||
final GridData selectorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
selectorCell.horizontalIndent = 2;
|
||||
selector.adaptToControl().setLayoutData(selectorCell);
|
||||
|
||||
final Label imageButton = this.widgetFactory.imageButton(
|
||||
ImageIcon.REMOVE_BOX,
|
||||
this,
|
||||
REMOVE_TEXT_KEY,
|
||||
null);
|
||||
final GridData actionCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
imageButton.setLayoutData(actionCell);
|
||||
|
||||
if (threshold != null) {
|
||||
if (threshold.value != null) {
|
||||
valueInput.setText(Indicator.getDisplayValue(
|
||||
this.indicatorTypeSupplier.get(),
|
||||
threshold.value));
|
||||
}
|
||||
if (threshold.color != null) {
|
||||
selector.select(threshold.color);
|
||||
}
|
||||
}
|
||||
|
||||
final Entry entry = new Entry(valueInput, selector, imageButton);
|
||||
this.thresholds.add(entry);
|
||||
|
||||
this.updateAnchor.layout();
|
||||
PageService.updateScrolledComposite(this);
|
||||
}
|
||||
|
||||
private void removeThreshold(final Entry entry) {
|
||||
if (this.thresholds.remove(entry)) {
|
||||
entry.dispose();
|
||||
}
|
||||
|
||||
this.updateAnchor.layout();
|
||||
PageService.updateScrolledComposite(this);
|
||||
}
|
||||
|
||||
private void adaptColumnWidth(final Event event) {
|
||||
try {
|
||||
final int currentTableWidth = this.getClientArea().width;
|
||||
final int dynWidth = currentTableWidth - ACTION_COLUMN_WIDTH;
|
||||
final int colWidth = dynWidth / 2;
|
||||
this.valueCell.widthHint = colWidth;
|
||||
this.colorCell.widthHint = colWidth;
|
||||
this.layout();
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to adaptColumnWidth: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
final Text valueInput;
|
||||
final Selection colorSelector;
|
||||
final Label removeButton;
|
||||
|
||||
Entry(final Text valueInput, final Selection colorSelector, final Label removeButton) {
|
||||
super();
|
||||
this.valueInput = valueInput;
|
||||
this.colorSelector = colorSelector;
|
||||
this.removeButton = removeButton;
|
||||
removeButton.addListener(SWT.MouseDown, event -> removeThreshold(this));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
this.valueInput.dispose();
|
||||
this.colorSelector.adaptToControl().dispose();
|
||||
this.removeButton.dispose();
|
||||
}
|
||||
|
||||
Double getValue() {
|
||||
if (this.valueInput == null || StringUtils.isBlank(this.valueInput.getText())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Double.parseDouble(this.valueInput.getText());
|
||||
}
|
||||
|
||||
String getColor() {
|
||||
if (this.colorSelector == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.colorSelector.getSelectionValue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.gui.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
||||
|
||||
public final class ThresholdList extends Composite {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ThresholdList.class);
|
||||
private static final long serialVersionUID = -2305091471607040280L;
|
||||
private static final int ACTION_COLUMN_WIDTH = 20;
|
||||
|
||||
private static final String COLOR_SELECTION_TEXT_KEY = "sebserver.exam.indicator.thresholds.select.color";
|
||||
private static final LocTextKey VALUE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.thresholds.list.value");
|
||||
private static final LocTextKey VALUE_TOOLTIP_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.thresholds.list.value" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private static final LocTextKey COLOR_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.thresholds.list.color");
|
||||
private static final LocTextKey COLOR_TOOLTIP_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.thresholds.list.color" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private static final LocTextKey ADD_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.thresholds.list.add");
|
||||
private static final LocTextKey REMOVE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.thresholds.list.remove");
|
||||
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final Supplier<IndicatorType> indicatorTypeSupplier;
|
||||
private final List<Entry> thresholds = new ArrayList<>();
|
||||
|
||||
private final GridData valueCell;
|
||||
private final GridData colorCell;
|
||||
private final GridData actionCell;
|
||||
private final Composite updateAnchor;
|
||||
|
||||
ThresholdList(
|
||||
final Composite parent,
|
||||
final Composite updateAnchor,
|
||||
final WidgetFactory widgetFactory,
|
||||
final Supplier<IndicatorType> indicatorTypeSupplier) {
|
||||
|
||||
super(parent, SWT.NONE);
|
||||
this.indicatorTypeSupplier = indicatorTypeSupplier;
|
||||
this.updateAnchor = updateAnchor;
|
||||
this.widgetFactory = widgetFactory;
|
||||
super.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
final GridLayout gridLayout = new GridLayout(3, false);
|
||||
gridLayout.verticalSpacing = 1;
|
||||
gridLayout.marginLeft = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
gridLayout.horizontalSpacing = 0;
|
||||
setLayout(gridLayout);
|
||||
|
||||
this.addListener(SWT.Resize, this::adaptColumnWidth);
|
||||
|
||||
final Label valueTitle = widgetFactory.labelLocalized(
|
||||
this,
|
||||
CustomVariant.TITLE_LABEL,
|
||||
VALUE_TEXT_KEY,
|
||||
VALUE_TOOLTIP_TEXT_KEY);
|
||||
this.valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
valueTitle.setLayoutData(this.valueCell);
|
||||
|
||||
final Label colorTitle = widgetFactory.labelLocalized(
|
||||
this,
|
||||
CustomVariant.TITLE_LABEL,
|
||||
COLOR_TEXT_KEY,
|
||||
COLOR_TOOLTIP_TEXT_KEY);
|
||||
this.colorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
colorTitle.setLayoutData(this.colorCell);
|
||||
|
||||
final Label imageButton = widgetFactory.imageButton(
|
||||
ImageIcon.ADD_BOX,
|
||||
this,
|
||||
ADD_TEXT_KEY,
|
||||
this::addThreshold);
|
||||
this.actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
|
||||
imageButton.setLayoutData(this.actionCell);
|
||||
}
|
||||
|
||||
public void setThresholds(final Collection<Threshold> thresholds) {
|
||||
clearList();
|
||||
if (thresholds != null) {
|
||||
thresholds.forEach(this::addThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Threshold> getThresholds() {
|
||||
removeInvalidListEntries();
|
||||
return this.thresholds
|
||||
.stream()
|
||||
.map(entry -> new Threshold(entry.getValue(), entry.getColor()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void removeInvalidListEntries() {
|
||||
this.thresholds
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue() == null || StringUtils.isBlank(entry.getColor()))
|
||||
.collect(Collectors.toList())
|
||||
.forEach(this::removeThreshold);
|
||||
}
|
||||
|
||||
private void clearList() {
|
||||
this.thresholds.forEach(Entry::dispose);
|
||||
this.thresholds.clear();
|
||||
}
|
||||
|
||||
private void addThreshold(final Event event) {
|
||||
addThreshold((Threshold) null);
|
||||
}
|
||||
|
||||
private void addThreshold(final Threshold threshold) {
|
||||
final Text valueInput = this.widgetFactory.numberInput(
|
||||
this, s -> {
|
||||
if (this.indicatorTypeSupplier.get().integerValue) {
|
||||
Integer.parseInt(s);
|
||||
} else {
|
||||
Double.parseDouble(s);
|
||||
}
|
||||
});
|
||||
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
valueInput.setLayoutData(valueCell);
|
||||
|
||||
final Selection selector = this.widgetFactory.selectionLocalized(
|
||||
Type.COLOR, this, null, null, null,
|
||||
COLOR_SELECTION_TEXT_KEY);
|
||||
final GridData selectorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
selectorCell.horizontalIndent = 2;
|
||||
selector.adaptToControl().setLayoutData(selectorCell);
|
||||
|
||||
final Label imageButton = this.widgetFactory.imageButton(
|
||||
ImageIcon.REMOVE_BOX,
|
||||
this,
|
||||
REMOVE_TEXT_KEY,
|
||||
null);
|
||||
final GridData actionCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
imageButton.setLayoutData(actionCell);
|
||||
|
||||
if (threshold != null) {
|
||||
if (threshold.value != null) {
|
||||
valueInput.setText(Indicator.getDisplayValue(
|
||||
this.indicatorTypeSupplier.get(),
|
||||
threshold.value));
|
||||
}
|
||||
if (threshold.color != null) {
|
||||
selector.select(threshold.color);
|
||||
}
|
||||
}
|
||||
|
||||
final Entry entry = new Entry(valueInput, selector, imageButton);
|
||||
this.thresholds.add(entry);
|
||||
|
||||
this.updateAnchor.layout();
|
||||
PageService.updateScrolledComposite(this);
|
||||
}
|
||||
|
||||
private void removeThreshold(final Entry entry) {
|
||||
if (this.thresholds.remove(entry)) {
|
||||
entry.dispose();
|
||||
}
|
||||
|
||||
this.updateAnchor.layout();
|
||||
PageService.updateScrolledComposite(this);
|
||||
}
|
||||
|
||||
private void adaptColumnWidth(final Event event) {
|
||||
try {
|
||||
final int currentTableWidth = this.getClientArea().width;
|
||||
final int dynWidth = currentTableWidth - ACTION_COLUMN_WIDTH;
|
||||
final int colWidth = dynWidth / 2;
|
||||
this.valueCell.widthHint = colWidth;
|
||||
this.colorCell.widthHint = colWidth;
|
||||
this.layout();
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to adaptColumnWidth: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
final Text valueInput;
|
||||
final Selection colorSelector;
|
||||
final Label removeButton;
|
||||
|
||||
Entry(final Text valueInput, final Selection colorSelector, final Label removeButton) {
|
||||
super();
|
||||
this.valueInput = valueInput;
|
||||
this.colorSelector = colorSelector;
|
||||
this.removeButton = removeButton;
|
||||
removeButton.addListener(SWT.MouseDown, event -> removeThreshold(this));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
this.valueInput.dispose();
|
||||
this.colorSelector.adaptToControl().dispose();
|
||||
this.removeButton.dispose();
|
||||
}
|
||||
|
||||
Double getValue() {
|
||||
if (this.valueInput == null || StringUtils.isBlank(this.valueInput.getText())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Double.parseDouble(this.valueInput.getText());
|
||||
}
|
||||
|
||||
String getColor() {
|
||||
if (this.colorSelector == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.colorSelector.getSelectionValue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,293 +1,290 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.servicelayer.lms.impl.moodle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
|
||||
|
||||
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
||||
*
|
||||
* See also: https://docs.moodle.org/dev/Web_service_API_functions */
|
||||
public class MoodleCourseAccess extends CourseAccess {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MoodleCourseAccess.class);
|
||||
|
||||
private static final String MOOLDE_QUIZ_START_URL_PATH = "/mod/quiz/view.php?id=";
|
||||
private static final String MOODLE_COURSE_API_FUNCTION_NAME = "core_course_get_courses";
|
||||
private static final String MOODLE_QUIZ_API_FUNCTION_NAME = "mod_quiz_get_quizzes_by_courses";
|
||||
|
||||
private final JSONMapper jsonMapper;
|
||||
private final LmsSetup lmsSetup;
|
||||
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
|
||||
|
||||
private MoodleAPIRestTemplate restTemplate;
|
||||
|
||||
protected MoodleCourseAccess(
|
||||
final JSONMapper jsonMapper,
|
||||
final LmsSetup lmsSetup,
|
||||
final MoodleRestTemplateFactory moodleRestTemplateFactory,
|
||||
final AsyncService asyncService) {
|
||||
|
||||
super(asyncService);
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
||||
}
|
||||
|
||||
LmsSetupTestResult initAPIAccess() {
|
||||
|
||||
final LmsSetupTestResult attributesCheck = this.moodleRestTemplateFactory.test();
|
||||
if (!attributesCheck.isOk()) {
|
||||
return attributesCheck;
|
||||
}
|
||||
|
||||
final Result<MoodleAPIRestTemplate> restTemplateRequest = getRestTemplate();
|
||||
if (restTemplateRequest.hasError()) {
|
||||
final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
|
||||
this.moodleRestTemplateFactory.knownTokenAccessPaths;
|
||||
log.error(message, restTemplateRequest.getError().getMessage());
|
||||
return LmsSetupTestResult.ofTokenRequestError(message);
|
||||
}
|
||||
|
||||
final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get();
|
||||
|
||||
try {
|
||||
restTemplate.testAPIConnection(
|
||||
MOODLE_COURSE_API_FUNCTION_NAME,
|
||||
MOODLE_QUIZ_API_FUNCTION_NAME);
|
||||
} catch (final RuntimeException e) {
|
||||
log.error("Failed to access Open edX course API: ", e);
|
||||
return LmsSetupTestResult.ofQuizAccessAPIError(e.getMessage());
|
||||
}
|
||||
|
||||
return LmsSetupTestResult.ofOkay();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<List<QuizData>> allQuizzesSupplier() {
|
||||
return () -> {
|
||||
return getRestTemplate()
|
||||
.map(this::collectAllQuizzes)
|
||||
.getOrThrow();
|
||||
};
|
||||
}
|
||||
|
||||
private ArrayList<QuizData> collectAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
|
||||
final String urlPrefix = this.lmsSetup.lmsApiUrl + MOOLDE_QUIZ_START_URL_PATH;
|
||||
return collectAllCourses(
|
||||
restTemplate)
|
||||
.stream()
|
||||
.reduce(
|
||||
new ArrayList<QuizData>(),
|
||||
(list, courseData) -> {
|
||||
list.addAll(quizDataOf(
|
||||
this.lmsSetup,
|
||||
courseData,
|
||||
urlPrefix));
|
||||
return list;
|
||||
},
|
||||
(list1, list2) -> {
|
||||
list1.addAll(list2);
|
||||
return list1;
|
||||
});
|
||||
}
|
||||
|
||||
private List<CourseData> collectAllCourses(final MoodleAPIRestTemplate restTemplate) {
|
||||
|
||||
try {
|
||||
|
||||
// first get courses form Moodle...
|
||||
final String coursesJSON = restTemplate.callMoodleAPIFunction(MOODLE_COURSE_API_FUNCTION_NAME);
|
||||
final Map<String, CourseData> courseData = this.jsonMapper.<Collection<CourseData>> readValue(
|
||||
coursesJSON,
|
||||
new TypeReference<Collection<CourseData>>() {
|
||||
})
|
||||
.stream()
|
||||
.collect(Collectors.toMap(d -> d.id, Function.identity()));
|
||||
|
||||
// then get all quizzes of courses and filter
|
||||
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
||||
attributes.put("courseids", new ArrayList<>(courseData.keySet()));
|
||||
|
||||
final String quizzesJSON = restTemplate.callMoodleAPIFunction(
|
||||
MOODLE_QUIZ_API_FUNCTION_NAME,
|
||||
attributes);
|
||||
|
||||
final CourseQuizData courseQuizData = this.jsonMapper.readValue(
|
||||
quizzesJSON,
|
||||
CourseQuizData.class);
|
||||
|
||||
courseQuizData.quizzes
|
||||
.stream()
|
||||
.forEach(quiz -> {
|
||||
final CourseData course = courseData.get(quiz.course);
|
||||
if (course != null) {
|
||||
course.quizzes.add(quiz);
|
||||
}
|
||||
});
|
||||
|
||||
return courseData.values()
|
||||
.stream()
|
||||
.filter(c -> !c.quizzes.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Unexpected exception while trying to get course data: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<QuizData> quizDataOf(
|
||||
final LmsSetup lmsSetup,
|
||||
final CourseData courseData,
|
||||
final String uriPrefix) {
|
||||
|
||||
final Map<String, String> additionalAttrs = new HashMap<>();
|
||||
additionalAttrs.clear();
|
||||
additionalAttrs.put("timecreated", String.valueOf(courseData.timecreated));
|
||||
additionalAttrs.put("course_shortname", courseData.shortname);
|
||||
additionalAttrs.put("course_fullname", courseData.fullname);
|
||||
additionalAttrs.put("course_displayname", courseData.displayname);
|
||||
additionalAttrs.put("course_summary", courseData.summary);
|
||||
|
||||
return courseData.quizzes
|
||||
.stream()
|
||||
.map(courseQuizData -> {
|
||||
final String startURI = uriPrefix + courseData.id;
|
||||
additionalAttrs.put("timelimit", String.valueOf(courseQuizData.timelimit));
|
||||
return new QuizData(
|
||||
courseQuizData.id,
|
||||
lmsSetup.getInstitutionId(),
|
||||
lmsSetup.id,
|
||||
lmsSetup.getLmsType(),
|
||||
courseQuizData.name,
|
||||
courseQuizData.intro,
|
||||
Utils.toDateTimeUTCUnix(courseData.startdate),
|
||||
Utils.toDateTimeUTCUnix(courseData.enddate),
|
||||
startURI,
|
||||
additionalAttrs);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
private Result<MoodleAPIRestTemplate> getRestTemplate() {
|
||||
if (this.restTemplate == null) {
|
||||
final Result<MoodleAPIRestTemplate> templateRequest = this.moodleRestTemplateFactory
|
||||
.createRestTemplate();
|
||||
if (templateRequest.hasError()) {
|
||||
return templateRequest;
|
||||
} else {
|
||||
this.restTemplate = templateRequest.get();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.of(this.restTemplate);
|
||||
}
|
||||
|
||||
/** Maps the Moodle course API course data */
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class CourseData {
|
||||
final String id;
|
||||
final String shortname;
|
||||
final String fullname;
|
||||
final String displayname;
|
||||
final String summary;
|
||||
final Long startdate; // unixtime milliseconds UTC
|
||||
final Long enddate; // unixtime milliseconds UTC
|
||||
final Long timecreated; // unixtime milliseconds UTC
|
||||
final Collection<CourseQuiz> quizzes = new ArrayList<>();
|
||||
|
||||
@JsonCreator
|
||||
protected CourseData(
|
||||
@JsonProperty(value = "id") final String id,
|
||||
@JsonProperty(value = "shortname") final String shortname,
|
||||
@JsonProperty(value = "fullname") final String fullname,
|
||||
@JsonProperty(value = "displayname") final String displayname,
|
||||
@JsonProperty(value = "summary") final String summary,
|
||||
@JsonProperty(value = "startdate") final Long startdate,
|
||||
@JsonProperty(value = "enddate") final Long enddate,
|
||||
@JsonProperty(value = "timecreated") final Long timecreated) {
|
||||
|
||||
this.id = id;
|
||||
this.shortname = shortname;
|
||||
this.fullname = fullname;
|
||||
this.displayname = displayname;
|
||||
this.summary = summary;
|
||||
this.startdate = startdate;
|
||||
this.enddate = enddate;
|
||||
this.timecreated = timecreated;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class CourseQuizData {
|
||||
final Collection<CourseQuiz> quizzes;
|
||||
|
||||
@JsonCreator
|
||||
protected CourseQuizData(
|
||||
@JsonProperty(value = "quizzes") final Collection<CourseQuiz> quizzes) {
|
||||
|
||||
this.quizzes = quizzes;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class CourseQuiz {
|
||||
final String id;
|
||||
final String course;
|
||||
final String coursemodule;
|
||||
final String name;
|
||||
final String intro; // HTML
|
||||
final Long timelimit; // unixtime milliseconds UTC
|
||||
|
||||
@JsonCreator
|
||||
protected CourseQuiz(
|
||||
@JsonProperty(value = "id") final String id,
|
||||
@JsonProperty(value = "course") final String course,
|
||||
@JsonProperty(value = "coursemodule") final String coursemodule,
|
||||
@JsonProperty(value = "name") final String name,
|
||||
@JsonProperty(value = "intro") final String intro,
|
||||
@JsonProperty(value = "timelimit") final Long timelimit) {
|
||||
|
||||
this.id = id;
|
||||
this.course = course;
|
||||
this.coursemodule = coursemodule;
|
||||
this.name = name;
|
||||
this.intro = intro;
|
||||
this.timelimit = timelimit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2020 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.servicelayer.lms.impl.moodle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
|
||||
|
||||
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
||||
*
|
||||
* See also: https://docs.moodle.org/dev/Web_service_API_functions */
|
||||
public class MoodleCourseAccess extends CourseAccess {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MoodleCourseAccess.class);
|
||||
|
||||
private static final String MOODLE_QUIZ_START_URL_PATH = "/mod/quiz/view.php?id=";
|
||||
private static final String MOODLE_COURSE_API_FUNCTION_NAME = "core_course_get_courses";
|
||||
private static final String MOODLE_QUIZ_API_FUNCTION_NAME = "mod_quiz_get_quizzes_by_courses";
|
||||
private static final String MOODLE_COURSE_API_COURSE_IDS = "courseids";
|
||||
|
||||
private final JSONMapper jsonMapper;
|
||||
private final LmsSetup lmsSetup;
|
||||
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
|
||||
|
||||
private MoodleAPIRestTemplate restTemplate;
|
||||
|
||||
protected MoodleCourseAccess(
|
||||
final JSONMapper jsonMapper,
|
||||
final LmsSetup lmsSetup,
|
||||
final MoodleRestTemplateFactory moodleRestTemplateFactory,
|
||||
final AsyncService asyncService) {
|
||||
|
||||
super(asyncService);
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
||||
}
|
||||
|
||||
LmsSetupTestResult initAPIAccess() {
|
||||
|
||||
final LmsSetupTestResult attributesCheck = this.moodleRestTemplateFactory.test();
|
||||
if (!attributesCheck.isOk()) {
|
||||
return attributesCheck;
|
||||
}
|
||||
|
||||
final Result<MoodleAPIRestTemplate> restTemplateRequest = getRestTemplate();
|
||||
if (restTemplateRequest.hasError()) {
|
||||
final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
|
||||
this.moodleRestTemplateFactory.knownTokenAccessPaths;
|
||||
log.error(message, restTemplateRequest.getError().getMessage());
|
||||
return LmsSetupTestResult.ofTokenRequestError(message);
|
||||
}
|
||||
|
||||
final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get();
|
||||
|
||||
try {
|
||||
restTemplate.testAPIConnection(
|
||||
MOODLE_COURSE_API_FUNCTION_NAME,
|
||||
MOODLE_QUIZ_API_FUNCTION_NAME);
|
||||
} catch (final RuntimeException e) {
|
||||
log.error("Failed to access Open edX course API: ", e);
|
||||
return LmsSetupTestResult.ofQuizAccessAPIError(e.getMessage());
|
||||
}
|
||||
|
||||
return LmsSetupTestResult.ofOkay();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<List<QuizData>> allQuizzesSupplier() {
|
||||
return () -> getRestTemplate()
|
||||
.map(this::collectAllQuizzes)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
private ArrayList<QuizData> collectAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
|
||||
final String urlPrefix = this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH;
|
||||
return collectAllCourses(
|
||||
restTemplate)
|
||||
.stream()
|
||||
.reduce(
|
||||
new ArrayList<>(),
|
||||
(list, courseData) -> {
|
||||
list.addAll(quizDataOf(
|
||||
this.lmsSetup,
|
||||
courseData,
|
||||
urlPrefix));
|
||||
return list;
|
||||
},
|
||||
(list1, list2) -> {
|
||||
list1.addAll(list2);
|
||||
return list1;
|
||||
});
|
||||
}
|
||||
|
||||
private List<CourseData> collectAllCourses(final MoodleAPIRestTemplate restTemplate) {
|
||||
|
||||
try {
|
||||
|
||||
// first get courses form Moodle...
|
||||
final String coursesJSON = restTemplate.callMoodleAPIFunction(MOODLE_COURSE_API_FUNCTION_NAME);
|
||||
final Map<String, CourseData> courseData = this.jsonMapper.<Collection<CourseData>> readValue(
|
||||
coursesJSON,
|
||||
new TypeReference<Collection<CourseData>>() {
|
||||
})
|
||||
.stream()
|
||||
.collect(Collectors.toMap(d -> d.id, Function.identity()));
|
||||
|
||||
// then get all quizzes of courses and filter
|
||||
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
||||
attributes.put(MOODLE_COURSE_API_COURSE_IDS, new ArrayList<>(courseData.keySet()));
|
||||
|
||||
final String quizzesJSON = restTemplate.callMoodleAPIFunction(
|
||||
MOODLE_QUIZ_API_FUNCTION_NAME,
|
||||
attributes);
|
||||
|
||||
final CourseQuizData courseQuizData = this.jsonMapper.readValue(
|
||||
quizzesJSON,
|
||||
CourseQuizData.class);
|
||||
|
||||
courseQuizData.quizzes
|
||||
.forEach(quiz -> {
|
||||
final CourseData course = courseData.get(quiz.course);
|
||||
if (course != null) {
|
||||
course.quizzes.add(quiz);
|
||||
}
|
||||
});
|
||||
|
||||
return courseData.values()
|
||||
.stream()
|
||||
.filter(c -> !c.quizzes.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Unexpected exception while trying to get course data: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, String> additionalAttrs = new HashMap<>();
|
||||
private static List<QuizData> quizDataOf(
|
||||
final LmsSetup lmsSetup,
|
||||
final CourseData courseData,
|
||||
final String uriPrefix) {
|
||||
|
||||
additionalAttrs.clear();
|
||||
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_CREATION_TIME, String.valueOf(courseData.time_created));
|
||||
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_SHORT_NAME, courseData.short_name);
|
||||
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_FULL_NAME, courseData.full_name);
|
||||
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_DISPLAY_NAME, courseData.display_name);
|
||||
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_SUMMARY, courseData.summary);
|
||||
|
||||
return courseData.quizzes
|
||||
.stream()
|
||||
.map(courseQuizData -> {
|
||||
final String startURI = uriPrefix + courseData.id;
|
||||
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_TIME_LIMIT, String.valueOf(courseQuizData.time_limit));
|
||||
return new QuizData(
|
||||
courseQuizData.id,
|
||||
lmsSetup.getInstitutionId(),
|
||||
lmsSetup.id,
|
||||
lmsSetup.getLmsType(),
|
||||
courseQuizData.name,
|
||||
courseQuizData.intro,
|
||||
Utils.toDateTimeUTCUnix(courseData.start_date),
|
||||
Utils.toDateTimeUTCUnix(courseData.end_date),
|
||||
startURI,
|
||||
additionalAttrs);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Result<MoodleAPIRestTemplate> getRestTemplate() {
|
||||
if (this.restTemplate == null) {
|
||||
final Result<MoodleAPIRestTemplate> templateRequest = this.moodleRestTemplateFactory
|
||||
.createRestTemplate();
|
||||
if (templateRequest.hasError()) {
|
||||
return templateRequest;
|
||||
} else {
|
||||
this.restTemplate = templateRequest.get();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.of(this.restTemplate);
|
||||
}
|
||||
|
||||
/** Maps the Moodle course API course data */
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class CourseData {
|
||||
final String id;
|
||||
final String short_name;
|
||||
final String full_name;
|
||||
final String display_name;
|
||||
final String summary;
|
||||
final Long start_date; // unix-time milliseconds UTC
|
||||
final Long end_date; // unix-time milliseconds UTC
|
||||
final Long time_created; // unix-time milliseconds UTC
|
||||
final Collection<CourseQuiz> quizzes = new ArrayList<>();
|
||||
|
||||
@JsonCreator
|
||||
protected CourseData(
|
||||
@JsonProperty(value = "id") final String id,
|
||||
@JsonProperty(value = "shortname") final String short_name,
|
||||
@JsonProperty(value = "fullname") final String full_name,
|
||||
@JsonProperty(value = "displayname") final String display_name,
|
||||
@JsonProperty(value = "summary") final String summary,
|
||||
@JsonProperty(value = "startdate") final Long start_date,
|
||||
@JsonProperty(value = "enddate") final Long end_date,
|
||||
@JsonProperty(value = "timecreated") final Long time_created) {
|
||||
|
||||
this.id = id;
|
||||
this.short_name = short_name;
|
||||
this.full_name = full_name;
|
||||
this.display_name = display_name;
|
||||
this.summary = summary;
|
||||
this.start_date = start_date;
|
||||
this.end_date = end_date;
|
||||
this.time_created = time_created;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class CourseQuizData {
|
||||
final Collection<CourseQuiz> quizzes;
|
||||
|
||||
@JsonCreator
|
||||
protected CourseQuizData(
|
||||
@JsonProperty(value = "quizzes") final Collection<CourseQuiz> quizzes) {
|
||||
|
||||
this.quizzes = quizzes;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class CourseQuiz {
|
||||
final String id;
|
||||
final String course;
|
||||
final String course_module;
|
||||
final String name;
|
||||
final String intro; // HTML
|
||||
final Long time_limit; // unix-time milliseconds UTC
|
||||
|
||||
@JsonCreator
|
||||
protected CourseQuiz(
|
||||
@JsonProperty(value = "id") final String id,
|
||||
@JsonProperty(value = "course") final String course,
|
||||
@JsonProperty(value = "coursemodule") final String course_module,
|
||||
@JsonProperty(value = "name") final String name,
|
||||
@JsonProperty(value = "intro") final String intro,
|
||||
@JsonProperty(value = "timelimit") final Long time_limit) {
|
||||
|
||||
this.id = id;
|
||||
this.course = course;
|
||||
this.course_module = course_module;
|
||||
this.name = name;
|
||||
this.intro = intro;
|
||||
this.time_limit = time_limit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,320 +1,320 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.servicelayer.session.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamConfigUpdateServiceImpl.class);
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
private final ConfigurationDAO configurationDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final ExamSessionService examSessionService;
|
||||
private final ExamUpdateHandler examUpdateHandler;
|
||||
|
||||
protected ExamConfigUpdateServiceImpl(
|
||||
final ExamDAO examDAO,
|
||||
final ConfigurationDAO configurationDAO,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final ExamSessionService examSessionService,
|
||||
final ExamUpdateHandler examUpdateHandler) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.configurationDAO = configurationDAO;
|
||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.examSessionService = examSessionService;
|
||||
this.examUpdateHandler = examUpdateHandler;
|
||||
}
|
||||
|
||||
// processing:
|
||||
// check running exam integrity (No running exam with active SEB client-connection available)
|
||||
// if OK, create an update-id and for each exam, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
|
||||
// check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
|
||||
// store the new configuration values (into history) so that they take effect
|
||||
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
|
||||
// evict each Exam from cache and release the update-lock on DB
|
||||
@Override
|
||||
public Result<Collection<Long>> processExamConfigurationChange(final Long configurationNodeId) {
|
||||
|
||||
final String updateId = this.examUpdateHandler.createUpdateId();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Process SEB Exam Configuration update for: {} with update-id {}",
|
||||
configurationNodeId,
|
||||
updateId);
|
||||
}
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
// check running exam integrity (No running exam with active SEB client-connection available)
|
||||
final Collection<Long> examIdsFirstCheck = checkRunningExamIntegrity(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Involved exams on fist integrity check: {}", examIdsFirstCheck);
|
||||
}
|
||||
|
||||
// if OK, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
|
||||
final Collection<Exam> exams = lockForUpdate(examIdsFirstCheck, updateId)
|
||||
.stream()
|
||||
.map(Result::getOrThrow)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final Collection<Long> examsIds = exams
|
||||
.stream()
|
||||
.map(Exam::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Update-Lock successfully placed for all involved exams: {}", examsIds);
|
||||
}
|
||||
|
||||
// check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
|
||||
final Collection<Long> examIdsSecondCheck = checkRunningExamIntegrity(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
checkIntegrityDoubleCheck(
|
||||
examIdsFirstCheck,
|
||||
examIdsSecondCheck);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Involved exams on second integrity check: {}", examIdsSecondCheck);
|
||||
}
|
||||
|
||||
// store the new configuration values (into history) so that they take effect
|
||||
final Configuration configuration = this.configurationDAO
|
||||
.saveToHistory(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Successfully save SEB Exam Configuration: {}", configuration);
|
||||
}
|
||||
|
||||
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
|
||||
for (final Exam exam : exams) {
|
||||
if (exam.getStatus() == ExamStatus.RUNNING && BooleanUtils.isTrue(exam.lmsSebRestriction)) {
|
||||
this.examUpdateHandler
|
||||
.getSebRestrictionService()
|
||||
.applySebClientRestriction(exam)
|
||||
.onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t));
|
||||
}
|
||||
}
|
||||
|
||||
// evict each Exam from cache and release the update-lock on DB
|
||||
for (final Exam exam : exams) {
|
||||
this.examSessionService
|
||||
.flushCache(exam)
|
||||
.onError(t -> log.error("Failed to flush Exam from cache: {}", exam, t));
|
||||
}
|
||||
|
||||
// release the update-locks on involved exams
|
||||
for (final Long examId : examIdsFirstCheck) {
|
||||
this.examDAO.releaseLock(examId, updateId)
|
||||
.onError(t -> log.error("Failed to release lock for Exam: {}", examId, t));
|
||||
}
|
||||
|
||||
return examIdsFirstCheck;
|
||||
})
|
||||
.onError(t -> this.examDAO.forceUnlockAll(updateId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Result<T> processExamConfigurationMappingChange(
|
||||
final ExamConfigurationMap mapping,
|
||||
final Function<ExamConfigurationMap, Result<T>> changeAction) {
|
||||
|
||||
return this.examDAO.byPK(mapping.examId)
|
||||
.map(exam -> {
|
||||
|
||||
// if the exam is not currently running just apply the action
|
||||
if (exam.status != ExamStatus.RUNNING) {
|
||||
return changeAction
|
||||
.apply(mapping)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
// if the exam is running...
|
||||
final String updateId = this.examUpdateHandler.createUpdateId();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(
|
||||
"Process SEB Exam Configuration mapping update for: {} with update-id {}",
|
||||
mapping,
|
||||
updateId);
|
||||
}
|
||||
|
||||
// check if there are no active client connections for this exam
|
||||
checkActiveClientConnections(exam);
|
||||
|
||||
// lock the exam
|
||||
this.examDAO
|
||||
.placeLock(exam.id, updateId)
|
||||
.getOrThrow();
|
||||
|
||||
// check again if there are no new active client connections in the meantime
|
||||
checkActiveClientConnections(exam);
|
||||
|
||||
// apply the referenced change action. On error the change is rolled back and
|
||||
// this processing returns immediately with the error
|
||||
final T result = changeAction
|
||||
.apply(mapping)
|
||||
.onError(t -> log.error("Fauled to save exam configuration: {}",
|
||||
mapping.configurationNodeId))
|
||||
.getOrThrow();
|
||||
|
||||
// update seb client restriction if the feature is activated for the exam
|
||||
if (exam.lmsSebRestriction) {
|
||||
this.examUpdateHandler
|
||||
.getSebRestrictionService()
|
||||
.applySebClientRestriction(exam)
|
||||
.onError(t -> log.error(
|
||||
"Failed to update SEB Client restriction for Exam: {}",
|
||||
exam,
|
||||
t));
|
||||
}
|
||||
|
||||
// flush the exam cache. If there was an error during flush, it is logged but this process goes on
|
||||
// and the saved changes are not rolled back
|
||||
this.examSessionService
|
||||
.flushCache(exam)
|
||||
.onError(t -> log.error("Failed to flush cache for exam: {}", exam));
|
||||
|
||||
// release the exam lock
|
||||
this.examDAO
|
||||
.releaseLock(exam.id, updateId)
|
||||
.onError(t -> log.error("Failed to release lock for exam: {}", exam));
|
||||
|
||||
return result;
|
||||
})
|
||||
.onError(t -> this.examDAO.forceUnlock(mapping.examId));
|
||||
|
||||
}
|
||||
|
||||
private void checkActiveClientConnections(final Exam exam) {
|
||||
if (this.examSessionService.hasActiveSebClientConnections(exam.id)) {
|
||||
throw new APIMessage.APIMessageException(
|
||||
ErrorMessage.INTEGRITY_VALIDATION,
|
||||
"Integrity violation: There are currently active SEB Client connection.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceReleaseUpdateLocks(final Long configurationId) {
|
||||
|
||||
log.warn(" **** Force release of update-locks for all exams that are related to configuration: {}",
|
||||
configurationId);
|
||||
|
||||
try {
|
||||
final Configuration config = this.configurationDAO
|
||||
.byPK(configurationId)
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Long> involvedExams = this.examConfigurationMapDAO
|
||||
.getExamIdsForConfigNodeId(config.configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Long> examsIds = forceReleaseUpdateLocks(involvedExams)
|
||||
.stream()
|
||||
.map(Result::getOrThrow)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.info("Successfully released update-locks for exams: {}", examsIds);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to release update-locks for exam(s)", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Result<Long>> forceReleaseUpdateLocks(final Collection<Long> examIds) {
|
||||
return examIds
|
||||
.stream()
|
||||
.map(this.examDAO::forceUnlock)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<Long>> checkRunningExamIntegrity(final Long configurationNodeId) {
|
||||
final Collection<Long> involvedExams = this.examConfigurationMapDAO
|
||||
.getExamIdsForConfigNodeId(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
if (involvedExams == null || involvedExams.isEmpty()) {
|
||||
return Result.of(Collections.emptyList());
|
||||
}
|
||||
|
||||
// check if the configuration is attached to a running exams with active client connections
|
||||
final long activeConnections = involvedExams
|
||||
.stream()
|
||||
.flatMap(examId -> {
|
||||
return this.examSessionService.getConnectionData(examId, Objects::nonNull)
|
||||
.getOrThrow()
|
||||
.stream();
|
||||
})
|
||||
.filter(ExamSessionService::isActiveConnection)
|
||||
.count();
|
||||
|
||||
// if we have active SEB client connection on any running exam that
|
||||
// is involved within the specified configuration change, the change is denied
|
||||
if (activeConnections > 0) {
|
||||
return Result.ofError(new APIMessage.APIMessageException(
|
||||
ErrorMessage.INTEGRITY_VALIDATION,
|
||||
"Integrity violation: There are currently active SEB Client connection."));
|
||||
} else {
|
||||
// otherwise we return the involved identifiers exams to further processing
|
||||
return Result.of(involvedExams);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIntegrityDoubleCheck(
|
||||
final Collection<Long> examIdsFirstCheck,
|
||||
final Collection<Long> examIdsSecondCheck) {
|
||||
|
||||
if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) {
|
||||
throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: "
|
||||
+ examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Result<Exam>> lockForUpdate(final Collection<Long> examIds, final String update) {
|
||||
return examIds.stream()
|
||||
.map(id -> this.examDAO.placeLock(id, update))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.servicelayer.session.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamConfigUpdateServiceImpl.class);
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
private final ConfigurationDAO configurationDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final ExamSessionService examSessionService;
|
||||
private final ExamUpdateHandler examUpdateHandler;
|
||||
|
||||
protected ExamConfigUpdateServiceImpl(
|
||||
final ExamDAO examDAO,
|
||||
final ConfigurationDAO configurationDAO,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final ExamSessionService examSessionService,
|
||||
final ExamUpdateHandler examUpdateHandler) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.configurationDAO = configurationDAO;
|
||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.examSessionService = examSessionService;
|
||||
this.examUpdateHandler = examUpdateHandler;
|
||||
}
|
||||
|
||||
// processing:
|
||||
// check running exam integrity (No running exam with active SEB client-connection available)
|
||||
// if OK, create an update-id and for each exam, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
|
||||
// check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
|
||||
// store the new configuration values (into history) so that they take effect
|
||||
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
|
||||
// evict each Exam from cache and release the update-lock on DB
|
||||
@Override
|
||||
public Result<Collection<Long>> processExamConfigurationChange(final Long configurationNodeId) {
|
||||
|
||||
final String updateId = this.examUpdateHandler.createUpdateId();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Process SEB Exam Configuration update for: {} with update-id {}",
|
||||
configurationNodeId,
|
||||
updateId);
|
||||
}
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
// check running exam integrity (No running exam with active SEB client-connection available)
|
||||
final Collection<Long> examIdsFirstCheck = checkRunningExamIntegrity(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Involved exams on fist integrity check: {}", examIdsFirstCheck);
|
||||
}
|
||||
|
||||
// if OK, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
|
||||
final Collection<Exam> exams = lockForUpdate(examIdsFirstCheck, updateId)
|
||||
.stream()
|
||||
.map(Result::getOrThrow)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final Collection<Long> examsIds = exams
|
||||
.stream()
|
||||
.map(Exam::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Update-Lock successfully placed for all involved exams: {}", examsIds);
|
||||
}
|
||||
|
||||
// check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
|
||||
final Collection<Long> examIdsSecondCheck = checkRunningExamIntegrity(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
checkIntegrityDoubleCheck(
|
||||
examIdsFirstCheck,
|
||||
examIdsSecondCheck);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Involved exams on second integrity check: {}", examIdsSecondCheck);
|
||||
}
|
||||
|
||||
// store the new configuration values (into history) so that they take effect
|
||||
final Configuration configuration = this.configurationDAO
|
||||
.saveToHistory(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Successfully save SEB Exam Configuration: {}", configuration);
|
||||
}
|
||||
|
||||
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
|
||||
for (final Exam exam : exams) {
|
||||
if (exam.getStatus() == ExamStatus.RUNNING && BooleanUtils.isTrue(exam.lmsSebRestriction)) {
|
||||
this.examUpdateHandler
|
||||
.getSebRestrictionService()
|
||||
.applySebClientRestriction(exam)
|
||||
.onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t));
|
||||
}
|
||||
}
|
||||
|
||||
// evict each Exam from cache and release the update-lock on DB
|
||||
for (final Exam exam : exams) {
|
||||
this.examSessionService
|
||||
.flushCache(exam)
|
||||
.onError(t -> log.error("Failed to flush Exam from cache: {}", exam, t));
|
||||
}
|
||||
|
||||
// release the update-locks on involved exams
|
||||
for (final Long examId : examIdsFirstCheck) {
|
||||
this.examDAO.releaseLock(examId, updateId)
|
||||
.onError(t -> log.error("Failed to release lock for Exam: {}", examId, t));
|
||||
}
|
||||
|
||||
return examIdsFirstCheck;
|
||||
})
|
||||
.onError(t -> this.examDAO.forceUnlockAll(updateId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Result<T> processExamConfigurationMappingChange(
|
||||
final ExamConfigurationMap mapping,
|
||||
final Function<ExamConfigurationMap, Result<T>> changeAction) {
|
||||
|
||||
return this.examDAO.byPK(mapping.examId)
|
||||
.map(exam -> {
|
||||
|
||||
// if the exam is not currently running just apply the action
|
||||
if (exam.status != ExamStatus.RUNNING) {
|
||||
return changeAction
|
||||
.apply(mapping)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
// if the exam is running...
|
||||
final String updateId = this.examUpdateHandler.createUpdateId();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(
|
||||
"Process SEB Exam Configuration mapping update for: {} with update-id {}",
|
||||
mapping,
|
||||
updateId);
|
||||
}
|
||||
|
||||
// check if there are no active client connections for this exam
|
||||
checkActiveClientConnections(exam);
|
||||
|
||||
// lock the exam
|
||||
this.examDAO
|
||||
.placeLock(exam.id, updateId)
|
||||
.getOrThrow();
|
||||
|
||||
// check again if there are no new active client connections in the meantime
|
||||
checkActiveClientConnections(exam);
|
||||
|
||||
// apply the referenced change action. On error the change is rolled back and
|
||||
// this processing returns immediately with the error
|
||||
final T result = changeAction
|
||||
.apply(mapping)
|
||||
.onError(t -> log.error("Failed to save exam configuration: {}",
|
||||
mapping.configurationNodeId))
|
||||
.getOrThrow();
|
||||
|
||||
// update seb client restriction if the feature is activated for the exam
|
||||
if (exam.lmsSebRestriction) {
|
||||
this.examUpdateHandler
|
||||
.getSebRestrictionService()
|
||||
.applySebClientRestriction(exam)
|
||||
.onError(t -> log.error(
|
||||
"Failed to update SEB Client restriction for Exam: {}",
|
||||
exam,
|
||||
t));
|
||||
}
|
||||
|
||||
// flush the exam cache. If there was an error during flush, it is logged but this process goes on
|
||||
// and the saved changes are not rolled back
|
||||
this.examSessionService
|
||||
.flushCache(exam)
|
||||
.onError(t -> log.error("Failed to flush cache for exam: {}", exam));
|
||||
|
||||
// release the exam lock
|
||||
this.examDAO
|
||||
.releaseLock(exam.id, updateId)
|
||||
.onError(t -> log.error("Failed to release lock for exam: {}", exam));
|
||||
|
||||
return result;
|
||||
})
|
||||
.onError(t -> this.examDAO.forceUnlock(mapping.examId));
|
||||
|
||||
}
|
||||
|
||||
private void checkActiveClientConnections(final Exam exam) {
|
||||
if (this.examSessionService.hasActiveSebClientConnections(exam.id)) {
|
||||
throw new APIMessage.APIMessageException(
|
||||
ErrorMessage.INTEGRITY_VALIDATION,
|
||||
"Integrity violation: There are currently active SEB Client connection.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceReleaseUpdateLocks(final Long configurationId) {
|
||||
|
||||
log.warn(" **** Force release of update-locks for all exams that are related to configuration: {}",
|
||||
configurationId);
|
||||
|
||||
try {
|
||||
final Configuration config = this.configurationDAO
|
||||
.byPK(configurationId)
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Long> involvedExams = this.examConfigurationMapDAO
|
||||
.getExamIdsForConfigNodeId(config.configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Long> examsIds = forceReleaseUpdateLocks(involvedExams)
|
||||
.stream()
|
||||
.map(Result::getOrThrow)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.info("Successfully released update-locks for exams: {}", examsIds);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to release update-locks for exam(s)", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Result<Long>> forceReleaseUpdateLocks(final Collection<Long> examIds) {
|
||||
return examIds
|
||||
.stream()
|
||||
.map(this.examDAO::forceUnlock)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<Long>> checkRunningExamIntegrity(final Long configurationNodeId) {
|
||||
final Collection<Long> involvedExams = this.examConfigurationMapDAO
|
||||
.getExamIdsForConfigNodeId(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
if (involvedExams == null || involvedExams.isEmpty()) {
|
||||
return Result.of(Collections.emptyList());
|
||||
}
|
||||
|
||||
// check if the configuration is attached to a running exams with active client connections
|
||||
final long activeConnections = involvedExams
|
||||
.stream()
|
||||
.flatMap(examId -> {
|
||||
return this.examSessionService.getConnectionData(examId, Objects::nonNull)
|
||||
.getOrThrow()
|
||||
.stream();
|
||||
})
|
||||
.filter(ExamSessionService::isActiveConnection)
|
||||
.count();
|
||||
|
||||
// if we have active SEB client connection on any running exam that
|
||||
// is involved within the specified configuration change, the change is denied
|
||||
if (activeConnections > 0) {
|
||||
return Result.ofError(new APIMessage.APIMessageException(
|
||||
ErrorMessage.INTEGRITY_VALIDATION,
|
||||
"Integrity violation: There are currently active SEB Client connection."));
|
||||
} else {
|
||||
// otherwise we return the involved identifiers exams to further processing
|
||||
return Result.of(involvedExams);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIntegrityDoubleCheck(
|
||||
final Collection<Long> examIdsFirstCheck,
|
||||
final Collection<Long> examIdsSecondCheck) {
|
||||
|
||||
if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) {
|
||||
throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: "
|
||||
+ examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Result<Exam>> lockForUpdate(final Collection<Long> examIds, final String update) {
|
||||
return examIds.stream()
|
||||
.map(id -> this.examDAO.placeLock(id, update))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,244 +1,245 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.weblayer.api;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Pair;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
@WebServiceProfile
|
||||
@RestController
|
||||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_CONFIGURATION_MAP_ENDPOINT)
|
||||
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
|
||||
|
||||
private final ExamDAO examDao;
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ExamConfigUpdateService examConfigUpdateService;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
||||
protected ExamConfigurationMappingController(
|
||||
final AuthorizationService authorization,
|
||||
final BulkActionService bulkActionService,
|
||||
final EntityDAO<ExamConfigurationMap, ExamConfigurationMap> entityDAO,
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ExamDAO examDao,
|
||||
final ConfigurationNodeDAO configurationNodeDAO,
|
||||
final ExamConfigUpdateService examConfigUpdateService,
|
||||
final ExamSessionService examSessionService) {
|
||||
|
||||
super(
|
||||
authorization,
|
||||
bulkActionService,
|
||||
entityDAO,
|
||||
userActivityLogDAO,
|
||||
paginationService,
|
||||
beanValidationService);
|
||||
|
||||
this.examDao = examDao;
|
||||
this.configurationNodeDAO = configurationNodeDAO;
|
||||
this.examConfigUpdateService = examConfigUpdateService;
|
||||
this.examSessionService = examSessionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExamConfigurationMap createNew(final POSTMapper postParams) {
|
||||
final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID);
|
||||
return new ExamConfigurationMap(institutionId, postParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SqlTable getSQLTableOfEntity() {
|
||||
return ExamConfigurationMapRecordDynamicSqlSupport.examConfigurationMapRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityType getGrantEntityType() {
|
||||
return EntityType.EXAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GrantEntity toGrantEntity(final ExamConfigurationMap entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.examDao
|
||||
.byPK(entity.examId)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ExamConfigurationMap> checkCreateAccess(final ExamConfigurationMap entity) {
|
||||
final GrantEntity grantEntity = toGrantEntity(entity);
|
||||
this.authorization.checkWrite(grantEntity);
|
||||
return Result.of(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ExamConfigurationMap> validForCreate(final ExamConfigurationMap entity) {
|
||||
return super.validForCreate(entity)
|
||||
.map(this::checkConfigurationState)
|
||||
.map(this::checkPasswordMatch)
|
||||
.map(this::checkNoActiveClientConnections);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequestMapping(
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public ExamConfigurationMap create(
|
||||
@RequestParam final MultiValueMap<String, String> allRequestParams,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||
|
||||
// check modify privilege for requested institution and concrete entityType
|
||||
this.checkModifyPrivilege(institutionId);
|
||||
final POSTMapper postMap = new POSTMapper(allRequestParams)
|
||||
.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
|
||||
|
||||
final ExamConfigurationMap requestModel = this.createNew(postMap);
|
||||
return this.checkCreateAccess(requestModel)
|
||||
.map(this::checkPasswordMatch)
|
||||
.flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
|
||||
entity,
|
||||
this.entityDAO::createNew))
|
||||
.flatMap(this::logCreate)
|
||||
.flatMap(this::notifyCreated)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT,
|
||||
method = RequestMethod.DELETE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public EntityProcessingReport hardDelete(@PathVariable final String modelId) {
|
||||
|
||||
return this.entityDAO.byModelId(modelId)
|
||||
.flatMap(this::checkWriteAccess)
|
||||
.flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
|
||||
entity,
|
||||
this::bulkDelete))
|
||||
.flatMap(this::notifyDeleted)
|
||||
.flatMap(pair -> this.logBulkAction(pair.b))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ExamConfigurationMap> notifyCreated(final ExamConfigurationMap entity) {
|
||||
// update the attached configurations state to "In Use"
|
||||
return this.configurationNodeDAO.save(new ConfigurationNode(
|
||||
entity.configurationNodeId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
ConfigurationStatus.IN_USE))
|
||||
.map(id -> entity);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<Pair<ExamConfigurationMap, EntityProcessingReport>> notifyDeleted(
|
||||
final Pair<ExamConfigurationMap, EntityProcessingReport> pair) {
|
||||
// update the attached configurations state to "Ready"
|
||||
return this.configurationNodeDAO.save(new ConfigurationNode(
|
||||
pair.a.configurationNodeId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
ConfigurationStatus.READY_TO_USE))
|
||||
.map(id -> pair);
|
||||
}
|
||||
|
||||
private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
|
||||
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
|
||||
throw new APIMessageException(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.EXAM_CONFIGURATION_MAP.TYPE_NAME,
|
||||
PasswordChange.ATTR_NAME_PASSWORD,
|
||||
"examConfigMapping:confirm_encrypt_secret:password.mismatch")));
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private ExamConfigurationMap checkConfigurationState(final ExamConfigurationMap entity) {
|
||||
final ConfigurationStatus status;
|
||||
if (entity.getConfigStatus() != null) {
|
||||
status = entity.getConfigStatus();
|
||||
} else {
|
||||
status = this.configurationNodeDAO.byPK(entity.configurationNodeId)
|
||||
.getOrThrow()
|
||||
.getStatus();
|
||||
}
|
||||
|
||||
if (status != ConfigurationStatus.READY_TO_USE) {
|
||||
throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
|
||||
"Illegal SEB Exam Configuration state"));
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private ExamConfigurationMap checkNoActiveClientConnections(final ExamConfigurationMap entity) {
|
||||
if (this.examSessionService.hasActiveSebClientConnections(entity.examId)) {
|
||||
throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
|
||||
"The Exam is currently running and has active SEB Client connections"));
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2019 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.weblayer.api;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Pair;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
@WebServiceProfile
|
||||
@RestController
|
||||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_CONFIGURATION_MAP_ENDPOINT)
|
||||
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
|
||||
|
||||
private final ExamDAO examDao;
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ExamConfigUpdateService examConfigUpdateService;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
||||
protected ExamConfigurationMappingController(
|
||||
final AuthorizationService authorization,
|
||||
final BulkActionService bulkActionService,
|
||||
final EntityDAO<ExamConfigurationMap, ExamConfigurationMap> entityDAO,
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ExamDAO examDao,
|
||||
final ConfigurationNodeDAO configurationNodeDAO,
|
||||
final ExamConfigUpdateService examConfigUpdateService,
|
||||
final ExamSessionService examSessionService) {
|
||||
|
||||
super(
|
||||
authorization,
|
||||
bulkActionService,
|
||||
entityDAO,
|
||||
userActivityLogDAO,
|
||||
paginationService,
|
||||
beanValidationService);
|
||||
|
||||
this.examDao = examDao;
|
||||
this.configurationNodeDAO = configurationNodeDAO;
|
||||
this.examConfigUpdateService = examConfigUpdateService;
|
||||
this.examSessionService = examSessionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExamConfigurationMap createNew(final POSTMapper postParams) {
|
||||
final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID);
|
||||
return new ExamConfigurationMap(institutionId, postParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SqlTable getSQLTableOfEntity() {
|
||||
return ExamConfigurationMapRecordDynamicSqlSupport.examConfigurationMapRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityType getGrantEntityType() {
|
||||
return EntityType.EXAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GrantEntity toGrantEntity(final ExamConfigurationMap entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.examDao
|
||||
.byPK(entity.examId)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ExamConfigurationMap> checkCreateAccess(final ExamConfigurationMap entity) {
|
||||
final GrantEntity grantEntity = toGrantEntity(entity);
|
||||
this.authorization.checkWrite(grantEntity);
|
||||
return Result.of(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ExamConfigurationMap> validForCreate(final ExamConfigurationMap entity) {
|
||||
return super.validForCreate(entity)
|
||||
.map(this::checkConfigurationState)
|
||||
.map(this::checkPasswordMatch)
|
||||
.map(this::checkNoActiveClientConnections);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequestMapping(
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public ExamConfigurationMap create(
|
||||
@RequestParam final MultiValueMap<String, String> allRequestParams,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||
|
||||
// check modify privilege for requested institution and concrete entityType
|
||||
this.checkModifyPrivilege(institutionId);
|
||||
final POSTMapper postMap = new POSTMapper(allRequestParams)
|
||||
.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
|
||||
|
||||
final ExamConfigurationMap requestModel = this.createNew(postMap);
|
||||
return this.checkCreateAccess(requestModel)
|
||||
.flatMap(this::validForCreate)
|
||||
.map(this::checkPasswordMatch)
|
||||
.flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
|
||||
entity,
|
||||
this.entityDAO::createNew))
|
||||
.flatMap(this::logCreate)
|
||||
.flatMap(this::notifyCreated)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT,
|
||||
method = RequestMethod.DELETE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public EntityProcessingReport hardDelete(@PathVariable final String modelId) {
|
||||
|
||||
return this.entityDAO.byModelId(modelId)
|
||||
.flatMap(this::checkWriteAccess)
|
||||
.flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
|
||||
entity,
|
||||
this::bulkDelete))
|
||||
.flatMap(this::notifyDeleted)
|
||||
.flatMap(pair -> this.logBulkAction(pair.b))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ExamConfigurationMap> notifyCreated(final ExamConfigurationMap entity) {
|
||||
// update the attached configurations state to "In Use"
|
||||
return this.configurationNodeDAO.save(new ConfigurationNode(
|
||||
entity.configurationNodeId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
ConfigurationStatus.IN_USE))
|
||||
.map(id -> entity);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<Pair<ExamConfigurationMap, EntityProcessingReport>> notifyDeleted(
|
||||
final Pair<ExamConfigurationMap, EntityProcessingReport> pair) {
|
||||
// update the attached configurations state to "Ready"
|
||||
return this.configurationNodeDAO.save(new ConfigurationNode(
|
||||
pair.a.configurationNodeId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
ConfigurationStatus.READY_TO_USE))
|
||||
.map(id -> pair);
|
||||
}
|
||||
|
||||
private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
|
||||
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
|
||||
throw new APIMessageException(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.EXAM_CONFIGURATION_MAP.TYPE_NAME,
|
||||
PasswordChange.ATTR_NAME_PASSWORD,
|
||||
"examConfigMapping:confirm_encrypt_secret:password.mismatch")));
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private ExamConfigurationMap checkConfigurationState(final ExamConfigurationMap entity) {
|
||||
final ConfigurationStatus status;
|
||||
if (entity.getConfigStatus() != null) {
|
||||
status = entity.getConfigStatus();
|
||||
} else {
|
||||
status = this.configurationNodeDAO.byPK(entity.configurationNodeId)
|
||||
.getOrThrow()
|
||||
.getStatus();
|
||||
}
|
||||
|
||||
if (status != ConfigurationStatus.READY_TO_USE) {
|
||||
throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
|
||||
"Illegal SEB Exam Configuration state"));
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private ExamConfigurationMap checkNoActiveClientConnections(final ExamConfigurationMap entity) {
|
||||
if (this.examSessionService.hasActiveSebClientConnections(entity.examId)) {
|
||||
throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
|
||||
"The Exam is currently running and has active SEB Client connections"));
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ sebserver.overall.about.markup=<span style='font-family: Arial, Helvetica,sans-s
|
|||
sebserver.overall.help=Documentation
|
||||
sebserver.overall.help.link=https://www.safeexambrowser.org/news_en.html
|
||||
|
||||
sebserver.overall.message.leave.without.save=You have unsaved changes!</br>Are you sure you want to leave the page? The changes will be lost.
|
||||
sebserver.overall.message.leave.without.save=You have unsaved changes!<br/>Are you sure you want to leave the page? The changes will be lost.
|
||||
sebserver.overall.upload=Please select a file
|
||||
sebserver.overall.upload.unsupported.file=This file type is not supported. Supported files are: {0}
|
||||
sebserver.overall.action.modify.cancel=Cancel
|
||||
|
@ -77,6 +77,7 @@ sebserver.form.validation.fieldError.size=The size must be between {3} and {4}
|
|||
sebserver.form.validation.fieldError.name=The Name is mandatory and must have a size between {3} and {4} character
|
||||
sebserver.form.validation.fieldError.urlSuffix=The URL Suffix must have a size between {3} and {4} character
|
||||
sebserver.form.validation.fieldError.notNull=This field is mandatory
|
||||
sebserver.form.validation.fieldError.name.notunique=This name is already in use. Please choose another one.
|
||||
sebserver.form.validation.fieldError.username.notunique=This Username is already in use. Please choose another one.
|
||||
sebserver.form.validation.fieldError.password.wrong=The old password is wrong
|
||||
sebserver.form.validation.fieldError.password.mismatch=The re-typed password doesn't match the new password
|
||||
|
@ -112,7 +113,7 @@ sebserver.login.failed.title=Login failed
|
|||
sebserver.login.failed.message=Access denied: wrong username or password
|
||||
sebserver.logout=Sign out
|
||||
sebserver.logout.success.message=You have been successfully signed out.
|
||||
sebserver.logout.invalid-session.message=You have been signed out because of a user session invalidation.</br>Please sign in again
|
||||
sebserver.logout.invalid-session.message=You have been signed out because of a user session invalidation.<br/>Please sign in again
|
||||
sebserver.login.password.change=Information
|
||||
sebserver.login.password.change.success=The password was successfully changed. Please sign in with your new password
|
||||
|
||||
|
@ -139,11 +140,11 @@ sebserver.institution.list.actions=
|
|||
sebserver.institution.list.empty=No institution has been found. Please adapt the filter or create a new institution
|
||||
sebserver.institution.list.title=Institutions
|
||||
sebserver.institution.list.column.name=Name
|
||||
sebserver.institution.list.column.name.tooltip=The name of the institution.</br></br>Use the filter above to narrow down a specific name.</br>{0}
|
||||
sebserver.institution.list.column.name.tooltip=The name of the institution.<br/><br/>Use the filter above to narrow down a specific name.<br/>{0}
|
||||
sebserver.institution.list.column.urlSuffix=URL Suffix
|
||||
sebserver.institution.list.column.urlSuffix.tooltip=The URL suffix to the institutional login page.</br></br>Use the filter above to narrow down a specific URL suffix.</br>{0}
|
||||
sebserver.institution.list.column.urlSuffix.tooltip=The URL suffix to the institutional login page.<br/><br/>Use the filter above to narrow down a specific URL suffix.<br/>{0}
|
||||
sebserver.institution.list.column.active=Status
|
||||
sebserver.institution.list.column.active.tooltip=The activity of the institution.</br></br>Use the filter above to specify the activity.</br>{0}
|
||||
sebserver.institution.list.column.active.tooltip=The activity of the institution.<br/><br/>Use the filter above to specify the activity.<br/>{0}
|
||||
|
||||
sebserver.institution.action.list=Institution
|
||||
sebserver.institution.action.form=Institution
|
||||
|
@ -164,9 +165,9 @@ sebserver.institution.form.title=Institution
|
|||
sebserver.institution.form.name=Name
|
||||
sebserver.institution.form.name.tooltip=The name of the institution
|
||||
sebserver.institution.form.urlSuffix=URL Suffix
|
||||
sebserver.institution.form.urlSuffix.tooltip=The URL suffix to the institutional login page.</br> Institutional URL is: http(s)://<seb-server-name>/<suffix>
|
||||
sebserver.institution.form.urlSuffix.tooltip=The URL suffix to the institutional login page.<br/> Institutional URL is: http(s)://<seb-server-name>/<suffix>
|
||||
sebserver.institution.form.logoImage=Logo Image
|
||||
sebserver.institution.form.logoImage.tooltip=The Image that is shown as a logo in the specified institutional login page.</br>In edit mode, use the arrow sign to open a upload dialog.
|
||||
sebserver.institution.form.logoImage.tooltip=The Image that is shown as a logo in the specified institutional login page.<br/>In edit mode, use the arrow sign to open a upload dialog.
|
||||
sebserver.institution.form.logoImage.unsupportedFileType=The selected file is not supported. Supported are: PNG and JPG
|
||||
|
||||
|
||||
|
@ -176,27 +177,27 @@ sebserver.institution.form.logoImage.unsupportedFileType=The selected file is no
|
|||
|
||||
sebserver.useraccount.list.actions=
|
||||
sebserver.useraccount.role.SEB_SERVER_ADMIN=SEB Server Administrator
|
||||
sebserver.useraccount.role.SEB_SERVER_ADMIN.tooltip=A SEB server administrator has overall read privileges</br>and is able to create new institutions and user accounts
|
||||
sebserver.useraccount.role.SEB_SERVER_ADMIN.tooltip=A SEB server administrator has overall read privileges<br/>and is able to create new institutions and user accounts
|
||||
sebserver.useraccount.role.INSTITUTIONAL_ADMIN=Institutional Administrator
|
||||
sebserver.useraccount.role.INSTITUTIONAL_ADMIN.tooltip=An institutional administrator has overall read privileges on the assigned institution</br>and is able to edit the institution and create new user accounts for the institution.</br>Furthermore an institutional administrator is able to create new LMS bindings and SEB client configurations for the institution.
|
||||
sebserver.useraccount.role.INSTITUTIONAL_ADMIN.tooltip=An institutional administrator has overall read privileges on the assigned institution<br/>and is able to edit the institution and create new user accounts for the institution.<br/>Furthermore an institutional administrator is able to create new LMS bindings and SEB client configurations for the institution.
|
||||
sebserver.useraccount.role.EXAM_ADMIN=Exam Administrator
|
||||
sebserver.useraccount.role.EXAM_ADMIN.tooltip=An exam administrator has overall read privileges for the institution but cannot see or manage other user accounts.</br>An exam administrator is able to create new SEB configurations and import and setup exams.
|
||||
sebserver.useraccount.role.EXAM_ADMIN.tooltip=An exam administrator has overall read privileges for the institution but cannot see or manage other user accounts.<br/>An exam administrator is able to create new SEB configurations and import and setup exams.
|
||||
sebserver.useraccount.role.EXAM_SUPPORTER=Exam Supporter
|
||||
sebserver.useraccount.role.EXAM_SUPPORTER.tooltip=An exam supporter can only see and edit the own user account</br> and monitor exams for that he/she was attached by an exam administrator.
|
||||
sebserver.useraccount.role.EXAM_SUPPORTER.tooltip=An exam supporter can only see and edit the own user account<br/> and monitor exams for that he/she was attached by an exam administrator.
|
||||
|
||||
sebserver.useraccount.list.empty=No user account has been found. Please adapt the filter or create a new user account
|
||||
sebserver.useraccount.list.title=User Accounts
|
||||
sebserver.useraccount.list.column.institution=Institution
|
||||
sebserver.useraccount.list.column.institution.tooltip=The institution of the user account.</br></br>Use the filter above to specify the institution.</br>{0}
|
||||
sebserver.useraccount.list.column.institution.tooltip=The institution of the user account.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.useraccount.list.column.name=First Name
|
||||
sebserver.useraccount.list.column.name.tooltip=The first name of the user.</br></br>Use the filter above to narrow down a specific first name.</br>{0}
|
||||
sebserver.useraccount.list.column.name.tooltip=The first name of the user.<br/><br/>Use the filter above to narrow down a specific first name.<br/>{0}
|
||||
sebserver.useraccount.list.column.username=User Name
|
||||
sebserver.useraccount.list.column.username.tooltip=The internal user name of the user.</br></br>Use the filter above to narrow down a specific user name.</br>{0}
|
||||
sebserver.useraccount.list.column.username.tooltip=The internal user name of the user.<br/><br/>Use the filter above to narrow down a specific user name.<br/>{0}
|
||||
sebserver.useraccount.list.column.email=Mail
|
||||
sebserver.useraccount.list.column.email.tooltip=The e-mail address of the user.</br></br>Use the filter above to narrow down a specific e-mail address.</br>{0}
|
||||
sebserver.useraccount.list.column.email.tooltip=The e-mail address of the user.<br/><br/>Use the filter above to narrow down a specific e-mail address.<br/>{0}
|
||||
sebserver.useraccount.list.column.language=Language
|
||||
sebserver.useraccount.list.column.active=Active
|
||||
sebserver.useraccount.list.column.active.tooltip=The activity of the user.</br></br>Use the filter above to specify the activity.</br>{0}
|
||||
sebserver.useraccount.list.column.active.tooltip=The activity of the user.<br/><br/>Use the filter above to specify the activity.<br/>{0}
|
||||
|
||||
sebserver.useraccount.action.list=User Account
|
||||
sebserver.useraccount.action.form=User Account of {0}
|
||||
|
@ -231,9 +232,9 @@ sebserver.useraccount.form.mail.tooltip=The e-mail address of the user.
|
|||
sebserver.useraccount.form.language=Language
|
||||
sebserver.useraccount.form.language.tooltip=The users language.
|
||||
sebserver.useraccount.form.timezone=Time Zone
|
||||
sebserver.useraccount.form.timezone.tooltip=The time-zone of the user.</br></br>Note that this also defines the exact time and date that is displayed to the user.
|
||||
sebserver.useraccount.form.timezone.tooltip=The time-zone of the user.<br/><br/>Note that this also defines the exact time and date that is displayed to the user.
|
||||
sebserver.useraccount.form.roles=User Roles
|
||||
sebserver.useraccount.form.roles.tooltip=Select the roles for the user.</br>A user can have more then one role but must have at least one.</br></br>Please use the tooltip on the role name for more information about a specific role.
|
||||
sebserver.useraccount.form.roles.tooltip=Select the roles for the user.<br/>A user can have more then one role but must have at least one.<br/><br/>Please use the tooltip on the role name for more information about a specific role.
|
||||
sebserver.useraccount.form.password=Password
|
||||
sebserver.useraccount.form.password.tooltip=The password of the user account
|
||||
sebserver.useraccount.form.password.confirm=Confirm Password
|
||||
|
@ -257,9 +258,13 @@ sebserver.lmssetup.list.action.no.modify.privilege=No Access: A LMS Setup from o
|
|||
sebserver.lmssetup.list.empty=No LMS Setup has been found. Please adapt the filter or create a new LMS Setup
|
||||
sebserver.lmssetup.list.title=Learning Management System Setups
|
||||
sebserver.lmssetup.list.column.institution=Institution
|
||||
sebserver.lmssetup.list.column.institution.tooltip=The institution of the LMS setup.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.lmssetup.list.column.name=Name
|
||||
sebserver.lmssetup.list.column.name.tooltip=The name of the LMS setup.<br/><br/>Use the filter above to narrow down a specific LMS by name.<br/>{0}
|
||||
sebserver.lmssetup.list.column.type=LMS Type
|
||||
sebserver.lmssetup.list.column.type.tooltip=The type of the LMS.<br/><br/>Use the filter above to specify the LMS type.<br/>{0}
|
||||
sebserver.lmssetup.list.column.active=Active
|
||||
sebserver.lmssetup.list.column.active.tooltip=The activity of the LMS Setup.<br/><br/>Use the filter above to specify the activity.<br/>{0}
|
||||
|
||||
sebserver.lmssetup.action.list=LMS Connection Settings
|
||||
sebserver.lmssetup.action.form=LMS Setup
|
||||
|
@ -270,7 +275,7 @@ sebserver.lmssetup.action.modify=Edit
|
|||
sebserver.lmssetup.action.savetest=Test And Save
|
||||
sebserver.lmssetup.action.testsave=Test And Save
|
||||
sebserver.lmssetup.action.test.ok=Successfully connected to the course API
|
||||
sebserver.lmssetup.action.test.tokenRequestError=The API access was denied: {0}
|
||||
sebserver.lmssetup.action.test.tokenRequestError=The API access was denied: {0}<br/>Please check the LMS connection details.
|
||||
sebserver.lmssetup.action.test.quizRequestError=Unable to request courses or quizzes from the course API of the LMS. {0}
|
||||
sebserver.lmssetup.action.test.quizRestrictionError=Unable to access course restriction API of the LMS. {0}
|
||||
sebserver.lmssetup.action.test.missingParameter=There is one or more missing connection parameter.<br/>Please check the connection parameter for this LMS Setup
|
||||
|
@ -285,17 +290,25 @@ sebserver.lmssetup.info.pleaseSelect=Please select first a LMS Setup from the li
|
|||
sebserver.lmssetup.form.title=Learning Management System Setup
|
||||
sebserver.lmssetup.form.title.new=Add Learning Management System Setup
|
||||
sebserver.lmssetup.form.institution=Institution
|
||||
sebserver.lmssetup.form.institution.tooltip=The institution where the LMS setup belongs to
|
||||
sebserver.lmssetup.form.name=Name
|
||||
sebserver.lmssetup.form.name.tooltip=The name of the LMS setup
|
||||
sebserver.lmssetup.form.type=Type
|
||||
sebserver.lmssetup.form.clientname.seb=SEB Auth. Name
|
||||
sebserver.lmssetup.form.secret.seb=SEB Auth. Password
|
||||
sebserver.lmssetup.form.type.tooltip=The type of the LMS Setup.
|
||||
sebserver.lmssetup.form.url=LMS Server Address
|
||||
sebserver.lmssetup.form.url.tooltip=The server URL to the specific LMS server.<br/><br/>This should point to the main- or root-address of the LMS server
|
||||
sebserver.lmssetup.form.clientname.lms=LMS Server Username
|
||||
sebserver.lmssetup.form.clientname.lms.tooltip=The client name of the API client access to the LMS.<br/><br/>This is usually provided by an LMS administrator that has created a API access account for SEB Server binding within the LMS server.
|
||||
sebserver.lmssetup.form.secret.lms=LMS Server Password
|
||||
sebserver.lmssetup.form.secret.lms.tooltip=The secret or password of the API client access to the LMS.<br/><br/>This is usually provided by an LMS administrator that has created a API access account for SEB Server binding within the LMS server.
|
||||
sebserver.lmssetup.form.proxy=Proxy
|
||||
sebserver.lmssetup.form.proxy.tooltip=The proxy details of a proxy is needed to connect to a LMS server
|
||||
sebserver.lmssetup.form.proxy.host=Proxy Host
|
||||
sebserver.lmssetup.form.proxy.host.tooltip=The server name of the proxy host to connect to
|
||||
sebserver.lmssetup.form.proxy.port=Proxy Port
|
||||
sebserver.lmssetup.form.proxy.port.tooltip=The proxy server port to connect to
|
||||
sebserver.lmssetup.form.proxy.auth-credentials=Proxy Name/Password
|
||||
sebserver.lmssetup.form.proxy.auth-credentials.tooltip=The proxy authentication credentials (name and password)<br/>to authenticate the connection within the proxy server
|
||||
|
||||
################################
|
||||
# Quiz Discovery
|
||||
|
@ -306,10 +319,15 @@ sebserver.quizdiscovery.list.actions=
|
|||
sebserver.quizdiscovery.list.title=Quizzes
|
||||
sebserver.quizdiscovery.list.empty=No Quiz has been found. Please adapt the filter or create a new LMS Setup
|
||||
sebserver.quizdiscovery.list.column.institution=Institution
|
||||
sebserver.quizdiscovery.list.column.institution.tooltip=The institution of the LMS setup that defines LMS of the quiz.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.quizdiscovery.list.column.lmssetup=LMS
|
||||
sebserver.quizdiscovery.list.column.lmssetup.tooltip=The LMS setup that defines the LMS of the quiz<br/><br/>Use the filter above to specify the LMS setup.<br/>{0}
|
||||
sebserver.quizdiscovery.list.column.name=Name
|
||||
sebserver.quizdiscovery.list.column.name.tooltip=The name of the quiz.<br/><br/>Use the filter above to narrow down a specific quiz name.<br/>{0}
|
||||
sebserver.quizdiscovery.list.column.starttime=Start Time {0}
|
||||
sebserver.quizdiscovery.list.column.starttime.tooltip=The start time of the quiz.<br/><br/>Use the filter above to set a specific from date.<br/>{0}
|
||||
sebserver.quizdiscovery.list.column.endtime=End Time {0}
|
||||
sebserver.quizdiscovery.list.column.endtime.tooltip=The end time of the quiz.<br/><br/>{0}
|
||||
sebserver.quizdiscovery.info.pleaseSelect=Please select first a Quiz from the list
|
||||
|
||||
sebserver.quizdiscovery.action.list=LMS Exam Discovery
|
||||
|
@ -318,16 +336,32 @@ sebserver.quizdiscovery.quiz.import.out.dated=The Selected Quiz is is already fi
|
|||
sebserver.quizdiscovery.action.details=Show Details
|
||||
|
||||
sebserver.quizdiscovery.quiz.details.title=Quiz Details
|
||||
sebserver.quizdiscovery.quiz.details.institution=Institution
|
||||
sebserver.quizdiscovery.quiz.details.institution.tooltip=The institution of the LMS setup that defines the LMS of the quiz.
|
||||
sebserver.quizdiscovery.quiz.details.lmssetup=LMS Setup
|
||||
sebserver.quizdiscovery.quiz.details.lmssetup.tooltip=The LMS setup that defines the LMS of the quiz.
|
||||
sebserver.quizdiscovery.quiz.details.name=Name
|
||||
sebserver.quizdiscovery.quiz.details.name.tooltip=The name of the quiz.<br/><br/>This name is defined on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.description=Description
|
||||
sebserver.quizdiscovery.quiz.details.description.tooltip=The description of the quiz.<br/><br/>This description is defined on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.starttime=Start Time
|
||||
sebserver.quizdiscovery.quiz.details.starttime.tooltip=The start time of the quiz.<br/><br/>This time is set on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.endtime=End Time
|
||||
sebserver.quizdiscovery.quiz.details.endtime.tooltip=The end time of the quiz.<br/><br/>This time is set on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.url=Start URL
|
||||
sebserver.quizdiscovery.quiz.details.url.tooltip=The start URL on the LMS for the quiz.<br/><br/>This is defined by the LMS setup and the quiz URL
|
||||
sebserver.quizdiscovery.quiz.details.additional.timecreated=Creation Time
|
||||
sebserver.quizdiscovery.quiz.details.additional.timecreated.tooltip=The time when the quiz was first created<br/><br/>This time is defined by the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_shortname=Short Name
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_shortname.tooltip=The short name of the quiz<br/><br/>This is defined on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_fullname=Full Name
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_fullname.tooltip=The full name of the quiz<br/><br/>This is defined on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_displayname=Display Name
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_displayname.tooltip=The display name of the quiz<br/><br/>This is defined on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_summary=Summary
|
||||
sebserver.quizdiscovery.quiz.details.additional.course_summary.tooltip=The summary of the quiz<br/><br/>This is defined on the corresponding LMS
|
||||
sebserver.quizdiscovery.quiz.details.additional.timelimit=Time Limit
|
||||
sebserver.quizdiscovery.quiz.details.additional.timelimit.toolitp=The time limit of the quiz<br/><br/>This is defined on the corresponding LMS
|
||||
|
||||
################################
|
||||
# Exam
|
||||
|
@ -336,10 +370,15 @@ sebserver.quizdiscovery.quiz.details.additional.timelimit=Time Limit
|
|||
sebserver.exam.list.actions=
|
||||
sebserver.exam.list.title=Exams
|
||||
sebserver.exam.list.column.institution=Institution
|
||||
sebserver.exam.list.column.institution.tooltip=The institution of the LMS setup that defines LMS of the exam.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.exam.list.column.lmssetup=LMS
|
||||
sebserver.exam.list.column.lmssetup.tooltip=The LMS setup that defines the LMS of the exam<br/><br/>Use the filter above to specify the LMS setup.<br/>{0}
|
||||
sebserver.exam.list.column.name=Name
|
||||
sebserver.exam.list.column.name.tooltip=The name of the exam.<br/><br/>Use the filter above to narrow down a specific exam name.<br/>{0}
|
||||
sebserver.exam.list.column.starttime=Start Time {0}
|
||||
sebserver.exam.list.column.starttime.tooltip=The start time of the exam.<br/><br/>Use the filter above to set a specific from date.<br/>{0}
|
||||
sebserver.exam.list.column.type=Type
|
||||
sebserver.exam.list.column.type.tooltip=The type of the exam.<br/><br/>Use the filter above to set a specific exam type.<br/>{0}
|
||||
|
||||
sebserver.exam.list.empty=No Exam has been found. Please adapt the filter or import one from Quiz
|
||||
sebserver.exam.list.modify.out.dated=Finished exams cannot be modified.
|
||||
|
@ -369,43 +408,73 @@ sebserver.exam.info.pleaseSelect=Please select first an Exam from the list
|
|||
sebserver.exam.form.title.import=Import Exam
|
||||
sebserver.exam.form.title=Exam
|
||||
sebserver.exam.form.lmssetup=LMS Setup
|
||||
sebserver.exam.form.lmssetup.tooltip=The LMS setup that defines the LMS of the exam.
|
||||
sebserver.exam.form.quizid=Quiz Identifier
|
||||
sebserver.exam.form.quizid.tooltip=The identifier that identifies the quiz of the exam on the corresponding LMS
|
||||
sebserver.exam.form.quizurl=Quiz URL
|
||||
sebserver.exam.form.quizurl.tooltip=The direct URL link to the quiz/exam on the LMS
|
||||
sebserver.exam.form.name=Name
|
||||
sebserver.exam.form.name.tooltip=The name of the exam.<br/><br/>This name is defined on the corresponding LMS
|
||||
sebserver.exam.form.description=Description
|
||||
sebserver.exam.form.description.tooltip=The description of the exam.<br/><br/>This description is defined on the corresponding LMS
|
||||
sebserver.exam.form.starttime=Start Time
|
||||
sebserver.exam.form.starttime.tooltip=The start time of the exam.<br/><br/>This time is set on the corresponding LMS
|
||||
sebserver.exam.form.endtime=End Time
|
||||
sebserver.exam.form.endtime.tooltip=The end time of the exam.<br/><br/>This time is set on the corresponding LMS
|
||||
sebserver.exam.form.status=Status
|
||||
sebserver.exam.form.status.tooltip=The current status for the exam.<br/><br/>Either "Up Coming" for an exam that has not yet been started,<br/>"Running" for an exam that is currently running<br/>or "Finished" for an exam that has already been finished yet
|
||||
sebserver.exam.form.type=Exam Type
|
||||
sebserver.exam.form.type.tooltip=The type of the exam.<br/><br/>This has only descriptive character for now and can be used to categorise exams within a type
|
||||
sebserver.exam.form.supporter=Exam Supporter
|
||||
sebserver.exam.form.supporter.action.add=Add as supporter for this exam
|
||||
sebserver.exam.form.supporter.action.remove=Remove supporter
|
||||
sebserver.exam.form.supporter.tooltip=A list of users that are allowed to support this exam.<br/><br/>To add a user in edit mode click into the field on right-hand and start typing the first letters of the username.<br/>A filtered choice will drop down. Click on a specific username on the drop-down to add the user to the list.<br/>To remove a user from the list, just double-click the username on the list.
|
||||
|
||||
sebserver.exam.form.sebrestriction.title=SEB Restriction Details
|
||||
sebserver.exam.form.sebrestriction.info=Info
|
||||
sebserver.exam.form.sebrestriction.info-text=A detailed description of the SEB restriction can be found within this page:<br/><a href="https://seb-openedx.readthedocs.io/en/latest/usage.html">https://seb-openedx.readthedocs.io/en/latest/usage.html</a>
|
||||
sebserver.exam.form.sebrestriction.configKeys=Config Keys
|
||||
sebserver.exam.form.sebrestriction.configKeys.tooltip=A comma-separated list of SEB Config Keys that are automatically generated from the attached SEB exam configurations<br/>and are checked by the LMS for the restricted SEB access for every request
|
||||
sebserver.exam.form.sebrestriction.browserExamKeys=Browser Exam Keys
|
||||
sebserver.exam.form.sebrestriction.browserExamKeys.tooltip=A comma-separated list of SEB Browser Exam Keys<br/>that are checked by the LMS for the restricted SEB access for every request
|
||||
sebserver.exam.form.sebrestriction.WHITELIST_PATHS=Component White-List
|
||||
sebserver.exam.form.sebrestriction.WHITELIST_PATHS.tooltip=Grant no-restriction to each of the given Open edX path components by select them for white-list.
|
||||
sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS=Chapters Black-List
|
||||
sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS.tooltip=Explicitly restrict a course chapter by adding the course-chapter-identifier to this comma-separated list
|
||||
sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS=Permissions
|
||||
sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS.tooltip=Define the additional SEB restriction permissions
|
||||
sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED=User Banning
|
||||
sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED.tooltip=Indicates whether the user of a restricted access shall be banned on authentication failure or not.
|
||||
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.ABOUT=About
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.ABOUT.tooltip=The "About" section of the Open edX course
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_OUTLINE=Course Outline
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_OUTLINE.tooltip=The outline section of the Open edX course
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_WARE=Course Ware
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_WARE.tooltip=The actual course and course content
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.DISCUSSION=Discussion
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.DISCUSSION.tooltip=The discussion section of the Open edX course
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.PROGRESS=Progress
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.PROGRESS.tooltip=The progress overview of the Open edX course
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.WIKI=Description (Wiki)
|
||||
sebserver.exam.form.sebrestriction.whiteListPaths.WIKI.tooltip=The wikipedia section of the Open edX course
|
||||
|
||||
sebserver.exam.form.sebrestriction.permissions.ALWAYS_ALLOW_STUFF=Stuff Role Always Allowed
|
||||
sebserver.exam.form.sebrestriction.permissions.ALWAYS_ALLOW_STUFF.tooltip=Set this to always allow none-restricted access for a user that has "stuff" privileges.
|
||||
sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_KEY=Check Browser-Exam-Key
|
||||
sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_KEY.tooltip=Always check received SEB Browser Exam Key with the defined ones for every request.
|
||||
sebserver.exam.form.sebrestriction.permissions.CHECK_CONFIG_KEY=Check Config-Key
|
||||
sebserver.exam.form.sebrestriction.permissions.CHECK_CONFIG_KEY.tooltip=Always check received SEB Config Key with the defined ones for every request.
|
||||
sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_OR_CONFIG_KEY=Check Browser-Exam- Or Config-Key
|
||||
sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_OR_CONFIG_KEY.tooltip=Always check either SEB Browser Exam Key or SEB Config Key with the defined ones for every request.
|
||||
|
||||
|
||||
sebserver.exam.type.UNDEFINED=Not Defined
|
||||
sebserver.exam.type.UNDEFINED.tooltip=No exam type specified
|
||||
sebserver.exam.type.MANAGED=Managed Devices
|
||||
sebserver.exam.type.MANAGED.tooltip=Exam type specified for managed devices
|
||||
sebserver.exam.type.BYOD=Bring Your Own Device
|
||||
sebserver.exam.type.BYOD.tooltip=Exam type specified for bring your own devices
|
||||
sebserver.exam.type.VDI=VDI (Virtual Desktop Infrastructure)
|
||||
sebserver.exam.type.VDI.tooltip=Exam type specified for Virtual Desktop Infrastructure
|
||||
|
||||
sebserver.exam.status.UP_COMING=Up Coming
|
||||
sebserver.exam.status.RUNNING=Running
|
||||
|
@ -413,9 +482,13 @@ sebserver.exam.status.FINISHED=Finished
|
|||
|
||||
sebserver.exam.configuration.list.actions=
|
||||
sebserver.exam.configuration.list.title=SEB Exam Configuration
|
||||
sebserver.exam.configuration.list.title.tooltip=A list of all attached SEB exam configuration for this exam.
|
||||
sebserver.exam.configuration.list.column.name=Name
|
||||
sebserver.exam.configuration.list.column.name.tooltip=The name of the attached SEB exam configuration.
|
||||
sebserver.exam.configuration.list.column.description=Description
|
||||
sebserver.exam.configuration.list.column.description.tooltip=The description of the attached SEB exam configuration.
|
||||
sebserver.exam.configuration.list.column.status=Status
|
||||
sebserver.exam.configuration.list.column.status.tooltip=The current status of the attached SEB exam configuration.
|
||||
sebserver.exam.configuration.list.empty=There is currently no SEB Configuration defined for this Exam. Please add one
|
||||
sebserver.exam.configuration.list.pleaseSelect=Please select first a SEB Configuration from the list
|
||||
sebserver.exam.configuration.action.noconfig.message=There is currently no SEB exam configuration to select.<br/>Please create one in SEB Configuration / Exam Configuration
|
||||
|
@ -431,25 +504,34 @@ sebserver.exam.configuration.action.get-config-key=Export Config-Key
|
|||
sebserver.exam.configuration.form.title.new=Add SEB Configuration Mapping
|
||||
sebserver.exam.configuration.form.title=SEB Configuration Mapping
|
||||
sebserver.exam.configuration.form.name=SEB Configuration
|
||||
sebserver.exam.configuration.form.name.tooltip=Please select a SEB exam configuration to attach to the exam
|
||||
sebserver.exam.configuration.form.encryptSecret=Encryption Password
|
||||
sebserver.exam.configuration.form.encryptSecret.tooltip=Define a encryption password if the SEB exam configuration should be encrypted by password
|
||||
sebserver.exam.configuration.form.description=Description
|
||||
sebserver.exam.configuration.form.description.tooltip=The description of the selected SEB exam configuration
|
||||
sebserver.exam.configuration.form.status=Status
|
||||
sebserver.exam.configuration.form.status.tooltip=The current status of the selected SEB exam configuration
|
||||
sebserver.exam.configuration.form.encryptSecret.confirm=Confirm Password
|
||||
sebserver.exam.configuration.form.encryptSecret.confirm.tooltip=Please confirm the encryption password if there is one
|
||||
|
||||
sebserver.exam.indicator.list.actions=
|
||||
sebserver.exam.indicator.list.title=Indicators
|
||||
sebserver.exam.indicator.list.title.tooltip=A list of indicators that are shown on the exam monitoring view
|
||||
sebserver.exam.indicator.list.column.type=Type
|
||||
sebserver.exam.indicator.list.column.type.tooltip=The type of indicator
|
||||
sebserver.exam.indicator.list.column.name=Name
|
||||
sebserver.exam.indicator.list.column.name.tooltip=The name of the indicator
|
||||
sebserver.exam.indicator.list.column.thresholds=Thresholds
|
||||
sebserver.exam.indicator.list.column.thresholds.tooltip=The thresholds of the indicator
|
||||
sebserver.exam.indicator.list.empty=There is currently no indicator defined for this exam. Please create a new one
|
||||
sebserver.exam.indicator.list.pleaseSelect=Please select first an indicator from the list
|
||||
|
||||
sebserver.exam.indicator.type.LAST_PING=Last Ping Time
|
||||
sebserver.exam.indicator.type.ERROR_COUNT=Errors
|
||||
sebserver.exam.indicator.type.WARN_COUNT=Warnings
|
||||
sebserver.exam.indicator.type.description.LAST_PING=This indicator shows the time in milliseconds since</br> the last ping has been received from a SEB Client.</br>This indicator can be used to track a SEB Client connection and indicate connection losse.</br></br>Thresholds are defined in milliseconds.
|
||||
sebserver.exam.indicator.type.description.ERROR_COUNT=This indicator shows the number of error log messages that</br> has been received from a SEB Client.</br>This indicator can be used to track errors of connected SEB Clients</br></br>Thresholds are defined by natural numbers.
|
||||
sebserver.exam.indicator.type.description.WARN_COUNT=This indicator shows the number of warn log messages that</br> has been received from a SEB Client.</br>This indicator can be used to track warnings of connected SEB Clients</br></br>Thresholds are defined by natural numbers.
|
||||
sebserver.exam.indicator.type.description.LAST_PING=This indicator shows the time in milliseconds since<br/> the last ping has been received from a SEB Client.<br/>This indicator can be used to track a SEB Client connection and indicate connection loss.<br/><br/>The value is in milliseconds.
|
||||
sebserver.exam.indicator.type.description.ERROR_COUNT=This indicator shows the number of error log messages that<br/> has been received from a SEB Client.<br/>This indicator can be used to track errors of connected SEB Clients<br/><br/>The value is natural numbers.
|
||||
sebserver.exam.indicator.type.description.WARN_COUNT=This indicator shows the number of warn log messages that<br/> has been received from a SEB Client.<br/>This indicator can be used to track warnings of connected SEB Clients<br/><br/>The value is natural numbers.
|
||||
|
||||
|
||||
sebserver.exam.indicator.info.pleaseSelect=Please select first an indicator from the list
|
||||
|
@ -462,19 +544,27 @@ sebserver.exam.indicator.action.save=Save
|
|||
sebserver.exam.indicator.form.title=Indicator
|
||||
sebserver.exam.indicator.form.title.new=Add Indicator
|
||||
sebserver.exam.indicator.form.exam=Exam
|
||||
sebserver.exam.indicator.form.exam.tooltip=The exam this indicator belongs to.
|
||||
sebserver.exam.indicator.form.name=Name
|
||||
sebserver.exam.indicator.form.name.tooltip=The name of the indicator.<br/><br/>This name is also displayed as the column title of the indicator on the exam monitoring view
|
||||
sebserver.exam.indicator.form.type=Type
|
||||
sebserver.exam.indicator.form.type.tooltip=The type of the indicator.<br/><br/>There are only a set of defined indicators to choose from.<br/>Choose one to see a detailed description for each indicator below.
|
||||
sebserver.exam.indicator.form.description=Type Description
|
||||
sebserver.exam.indicator.form.description.tooltip=A detailed description of the selected indicator.
|
||||
sebserver.exam.indicator.form.color=Default Color
|
||||
sebserver.exam.indicator.form.color.tooltip=The default color that is displayed on the exam monitoring for this indicator.
|
||||
sebserver.exam.indicator.form.color.action=Please select a color
|
||||
sebserver.exam.indicator.form.thresholds=Thresholds
|
||||
sebserver.exam.indicator.form.thresholds.tooltip=A list of value / color pairs that defines the thresholds of the indicator.<br/><br/>On the exam monitoring view a cell of the indicator is displayed in the specified color when the defined threshold value is reached
|
||||
sebserver.exam.indicator.thresholds.select.color=Please select a color
|
||||
|
||||
sebserver.exam.indicator.thresholds.list.title=Thresholds
|
||||
sebserver.exam.indicator.thresholds.list.value=Value
|
||||
sebserver.exam.indicator.thresholds.list.value.tooltip=The threshold value.
|
||||
sebserver.exam.indicator.thresholds.list.color=Color
|
||||
sebserver.exam.indicator.thresholds.list.add=Add Threshold
|
||||
sebserver.exam.indicator.thresholds.list.remove=Delete Threshold
|
||||
sebserver.exam.indicator.thresholds.list.color.tooltip=The color that is displayed on the exam monitoring view when indicator value has reached the defined threshold value.
|
||||
sebserver.exam.indicator.thresholds.list.add=Add a new threshold
|
||||
sebserver.exam.indicator.thresholds.list.remove=Delete this threshold
|
||||
|
||||
################################
|
||||
# SEB Client Configuration
|
||||
|
@ -489,28 +579,40 @@ sebserver.clientconfig.list.empty=There is currently no SEB-Client configuration
|
|||
sebserver.clientconfig.list.title=SEB Client Configurations
|
||||
sebserver.clientconfig.list.actions=
|
||||
sebserver.clientconfig.list.column.institution=Institution
|
||||
sebserver.clientconfig.list.column.institution.tooltip=The institution of the SEB client configuration.</br></br>Use the filter above to specify the institution.</br>{0}
|
||||
sebserver.clientconfig.list.column.institution.tooltip=The institution of the SEB client configuration.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.clientconfig.list.column.name=Name
|
||||
sebserver.clientconfig.list.column.name.tooltip=The name of the SEB client configuration.</br></br>Use the filter above to narrow down a specific name.</br>{0}
|
||||
sebserver.clientconfig.list.column.name.tooltip=The name of the SEB client configuration.<br/><br/>Use the filter above to narrow down a specific name.<br/>{0}
|
||||
sebserver.clientconfig.list.column.date=Creation Date {0}
|
||||
sebserver.clientconfig.list.column.date.tooltip=The date when the SEB client configuration was first created.</br></br>Use the filter above to specify a from-date.</br>{0}
|
||||
sebserver.clientconfig.list.column.date.tooltip=The date when the SEB client configuration was first created.<br/><br/>Use the filter above to specify a from-date.<br/>{0}
|
||||
sebserver.clientconfig.list.column.active=Active
|
||||
sebserver.clientconfig.list.column.active.tooltip=The activity of SEB client configuration.</br></br>Use the filter above to specify the activity.</br>{0}
|
||||
sebserver.clientconfig.list.column.active.tooltip=The activity of SEB client configuration.<br/><br/>Use the filter above to specify the activity.<br/>{0}
|
||||
sebserver.clientconfig.info.pleaseSelect=Please select first a Client Configuration from the list
|
||||
sebserver.clientconfig.list.action.no.modify.privilege=No Access: A SEB Client Configuration from other institution cannot be modified.
|
||||
|
||||
sebserver.clientconfig.form.title.new=Add Client Configuration
|
||||
sebserver.clientconfig.form.title=SEB Client Configuration
|
||||
sebserver.clientconfig.form.name=Name
|
||||
sebserver.clientconfig.form.name.tooltip=The name of the SEB Client Configuration.</br>Can be any name that not already exists for another SEB Client Configuration
|
||||
sebserver.clientconfig.form.name.tooltip=The name of the SEB Client Configuration.<br/>Can be any name that not already exists for another SEB Client Configuration
|
||||
sebserver.clientconfig.form.fallback=With Fallback
|
||||
sebserver.clientconfig.form.fallback.tooltip=Indicates whether this SEB Client Configuration has a fallback definition or not
|
||||
sebserver.clientconfig.form.fallback-url=Fallback Start URL
|
||||
sebserver.clientconfig.form.fallback-url.tooltip=A fallback URL that tells the SEB where to go when the SEB Server service is unavailable.
|
||||
sebserver.clientconfig.form.sebServerFallbackTimeout=Fallback Timeout
|
||||
sebserver.clientconfig.form.sebServerFallbackTimeout.tooltip=Defines the fallback timeout for the SEB Client in milli-seconds.
|
||||
sebserver.clientconfig.form.sebServerFallbackAttempts=Fallback Attempts
|
||||
sebserver.clientconfig.form.sebServerFallbackAttempts.tooltip=The number of connection attempts a SEB Client is trying before switching to fallback case.
|
||||
sebserver.clientconfig.form.sebServerFallbackAttemptInterval=Attempt Interval
|
||||
sebserver.clientconfig.form.sebServerFallbackAttemptInterval.tooltip=The interval (in milli-seconds) between connection attempts a SEB Client shall use.
|
||||
sebserver.clientconfig.form.sebServerFallbackPasswordHash=Fallback Password
|
||||
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip=A password if set, a SEB Client user must give before the SEB Client starts the fallback procedure.
|
||||
sebserver.clientconfig.form.date=Creation Date
|
||||
sebserver.clientconfig.form.date.tooltip=The date when the SEB client configuration was first created.
|
||||
sebserver.clientconfig.form.encryptSecret=Configuration Password
|
||||
sebserver.clientconfig.form.encryptSecret.tooltip=Define a password if the SEB client configuration shall be password-encrypted
|
||||
sebserver.clientconfig.form.encryptSecret.confirm=Confirm Password
|
||||
sebserver.clientconfig.form.encryptSecret.confirm.tooltip=Please retype the given password for configrmation
|
||||
sebserver.clientconfig.form.encryptSecret.confirm.tooltip=Please retype the given password for confirmation
|
||||
sebserver.clientconfig.form.sebConfigPurpose=Configuration Purpose
|
||||
sebserver.clientconfig.form.sebConfigPurpose.tooltip=This indicates whether this client configuration shall be used to configure the SEB Client or to start an exam
|
||||
|
||||
sebserver.clientconfig.action.list.new=Add Configuration
|
||||
sebserver.clientconfig.action.list.view=View Configuration
|
||||
|
@ -527,13 +629,13 @@ sebserver.clientconfig.action.deactivate=Deactivate Configuration
|
|||
sebserver.examconfig.action.list=Exam Configuration
|
||||
sebserver.examconfig.list.title=Exam Configurations
|
||||
sebserver.examconfig.list.column.institution=Institution
|
||||
sebserver.examconfig.list.column.institution.tooltip=The institution of the SEB exam configuration.</br></br>Use the filter above to specify the institution.</br>{0}
|
||||
sebserver.examconfig.list.column.institution.tooltip=The institution of the SEB exam configuration.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.examconfig.list.column.name=Name
|
||||
sebserver.examconfig.list.column.name.tooltip=The name of the SEB exam configuration.</br></br>Use the filter above to narrow down a specific name.</br>{0}
|
||||
sebserver.examconfig.list.column.name.tooltip=The name of the SEB exam configuration.<br/><br/>Use the filter above to narrow down a specific name.<br/>{0}
|
||||
sebserver.examconfig.list.column.description=Description
|
||||
sebserver.examconfig.list.column.description.tooltip=The description of the SEB exam configuration.</br></br>Use the filter above to find configurations that contains specific words or phrases within the description.</br>{0}
|
||||
sebserver.examconfig.list.column.description.tooltip=The description of the SEB exam configuration.<br/><br/>Use the filter above to find configurations that contains specific words or phrases within the description.<br/>{0}
|
||||
sebserver.examconfig.list.column.status=Status
|
||||
sebserver.examconfig.list.column.status.tooltip=The status of the SEB exam configuration.</br></br>Use the filter above to specify a status.</br>{0}
|
||||
sebserver.examconfig.list.column.status.tooltip=The status of the SEB exam configuration.<br/><br/>Use the filter above to specify a status.<br/>{0}
|
||||
|
||||
sebserver.examconfig.list.actions=
|
||||
|
||||
|
@ -577,10 +679,10 @@ sebserver.examconfig.form.with-history=With History
|
|||
sebserver.examconfig.form.template=Template
|
||||
sebserver.examconfig.form.template.tooltip=The template this SEB exam configuration depends on.
|
||||
sebserver.examconfig.form.status=Status
|
||||
sebserver.examconfig.form.status.tooltip=The status of this SEB exam configuration.</br></br>Under Construction marks a SEB exam configuration to not be able to attach to an exam so far.</br></br>Ready to use marks an SEB exam configuration to be able to attach to an exam.</br></br>In Use marks a SEB exam configuration is already been used from one or more exam(s)
|
||||
sebserver.examconfig.form.status.tooltip=The status of this SEB exam configuration.<br/><br/>Under Construction marks a SEB exam configuration to not be able to attach to an exam so far.<br/><br/>Ready to use marks an SEB exam configuration to be able to attach to an exam.<br/><br/>In Use marks a SEB exam configuration is already been used from one or more exam(s)
|
||||
sebserver.examconfig.form.config-key.title=Config Key
|
||||
sebserver.examconfig.form.attached-to=Attached To Exam
|
||||
sebserver.examconfig.form.attached-to.tooltip=This SEB exam configuration is currently attached to the following exams.</br></br>Select an exam from the list and use the "View Exam" or Double-Click on the list to go to a specific exam.
|
||||
sebserver.examconfig.form.attached-to.tooltip=This SEB exam configuration is currently attached to the following exams.<br/><br/>Select an exam from the list and use the "View Exam" or Double-Click on the list to go to a specific exam.
|
||||
|
||||
sebserver.examconfig.status.CONSTRUCTION=Under Construction
|
||||
sebserver.examconfig.status.READY_TO_USE=Ready To Use
|
||||
|
@ -603,9 +705,9 @@ sebserver.examconfig.props.form.views.hooked_keys=Hooked Keys
|
|||
sebserver.examconfig.props.label.hashedAdminPassword=Administrator password
|
||||
sebserver.examconfig.props.label.hashedAdminPassword.confirm=Confirm password
|
||||
sebserver.examconfig.props.label.allowQuit=Allow user to quit SEB
|
||||
sebserver.examconfig.props.label.allowQuit.tooltip=Users can quit SEB with Control-Q, window close or quit button.</br>Otherwise use a quit link in your exam system or shutdown/restart the computer.
|
||||
sebserver.examconfig.props.label.allowQuit.tooltip=Users can quit SEB with Control-Q, window close or quit button.<br/>Otherwise use a quit link in your exam system or shutdown/restart the computer.
|
||||
sebserver.examconfig.props.label.ignoreExitKeys=Ignore exit keys
|
||||
sebserver.examconfig.props.label.ignoreExitKeys.tooltip=SEB ignores the exit keys and can only be quit manually by entering the quit password.</br>(click Quit button in SEB taskbar, press Ctrl-Q or click the main browser window close button)
|
||||
sebserver.examconfig.props.label.ignoreExitKeys.tooltip=SEB ignores the exit keys and can only be quit manually by entering the quit password.<br/>(click Quit button in SEB taskbar, press Ctrl-Q or click the main browser window close button)
|
||||
sebserver.examconfig.props.label.hashedQuitPassword=Quit/unlock password
|
||||
sebserver.examconfig.props.label.hashedQuitPassword.confirm=Confirm password
|
||||
sebserver.examconfig.props.group.exitSequence=Exit Sequence
|
||||
|
@ -645,15 +747,15 @@ sebserver.examconfig.props.label.mainBrowserWindowPositioning.2=Right
|
|||
|
||||
sebserver.examconfig.props.group.wintoolbar=Browser Window Toolbar
|
||||
sebserver.examconfig.props.label.enableBrowserWindowToolbar=Enable browser window toolbar
|
||||
sebserver.examconfig.props.label.enableBrowserWindowToolbar.tooltip=Displays a toolbar on top of the browser window</br>which can also be hidden by the user.
|
||||
sebserver.examconfig.props.label.enableBrowserWindowToolbar.tooltip=Displays a toolbar on top of the browser window<br/>which can also be hidden by the user.
|
||||
sebserver.examconfig.props.label.hideBrowserWindowToolbar=Hide toolbar as default (Mac)
|
||||
sebserver.examconfig.props.label.hideBrowserWindowToolbar.tooltip=Hide browser window toolbar by default.</br>It can be shown again by using the View menu or Alt-Command-T.
|
||||
sebserver.examconfig.props.label.hideBrowserWindowToolbar.tooltip=Hide browser window toolbar by default.<br/>It can be shown again by using the View menu or Alt-Command-T.
|
||||
sebserver.examconfig.props.label.showMenuBar=Show menu bar (Mac)
|
||||
sebserver.examconfig.props.label.showMenuBar.tooltip=Show the OS X menu bar to allow to access settings like Wi-Fi.
|
||||
|
||||
sebserver.examconfig.props.group.taskbar=SEB Taskbar/Dock
|
||||
sebserver.examconfig.props.label.showTaskBar=Show SEB taskbar
|
||||
sebserver.examconfig.props.label.showTaskBar.tooltip=The SEB task bar shows and switches between open browser windows,</br> allowed resources and applications and displays additional controls
|
||||
sebserver.examconfig.props.label.showTaskBar.tooltip=The SEB task bar shows and switches between open browser windows,<br/> allowed resources and applications and displays additional controls
|
||||
sebserver.examconfig.props.label.taskBarHeight=Taskbar/dock height
|
||||
sebserver.examconfig.props.label.taskBarHeight.tooltip=Height of SEB dock/task bar in points/pixels
|
||||
sebserver.examconfig.props.label.showReloadButton=Show reload button
|
||||
|
@ -665,9 +767,9 @@ sebserver.examconfig.props.label.showInputLanguage.tooltip=Shows current keyboar
|
|||
|
||||
sebserver.examconfig.props.group.zoom=Enable Zoom (Win/Mac)
|
||||
sebserver.examconfig.props.label.enableZoomPage=Enable page zoom
|
||||
sebserver.examconfig.props.label.enableZoomPage.tooltip=Pages can be zoomed with ctrl - cmd +/-</br> or the commands in the view menu and browser window toolbar (Mac)
|
||||
sebserver.examconfig.props.label.enableZoomPage.tooltip=Pages can be zoomed with ctrl - cmd +/-<br/> or the commands in the view menu and browser window toolbar (Mac)
|
||||
sebserver.examconfig.props.label.enableZoomText=Enable text zoom
|
||||
sebserver.examconfig.props.label.enableZoomText.tooltip=Text in browser windows can be zoomed with cmd +/-</br> or the commands in the view menu and browser window toolbar (Mac)
|
||||
sebserver.examconfig.props.label.enableZoomText.tooltip=Text in browser windows can be zoomed with cmd +/-<br/> or the commands in the view menu and browser window toolbar (Mac)
|
||||
sebserver.examconfig.props.group.zoomMode=Zoom Mode Win (Ctrl-Mousewheel)
|
||||
sebserver.examconfig.props.label.zoomMode.0=Use page zoom
|
||||
sebserver.examconfig.props.label.zoomMode.0.tooltip=Zoom whole web pages using Ctrl-Mousewheel (Win)"
|
||||
|
@ -687,7 +789,7 @@ sebserver.examconfig.props.label.allowSpellCheck=Allow spell checking
|
|||
sebserver.examconfig.props.label.allowSpellCheck.tooltip=Allow to use "Check spelling" in the SEB browser
|
||||
sebserver.examconfig.props.label.allowDictionaryLookup=Allow dictionary lookup (Mac)
|
||||
sebserver.examconfig.props.label.allowDictionaryLookup.tooltip=Allow to use the OS X dictionary lookup using a 3 finger tap
|
||||
sebserver.examconfig.props.label.allowSpellCheckDictionary=The list below shows all dictionaries currently available for spell checking.</br>SEB comes with a list of standard dictionaries that can be activated/deactivated here.
|
||||
sebserver.examconfig.props.label.allowSpellCheckDictionary=The list below shows all dictionaries currently available for spell checking.<br/>SEB comes with a list of standard dictionaries that can be activated/deactivated here.
|
||||
sebserver.examconfig.props.label.allowSpellCheckDictionary.da-DK=Danish (Denmark) (da-DK)
|
||||
sebserver.examconfig.props.label.allowSpellCheckDictionary.en-AU=English (Australia) (en-AU)
|
||||
sebserver.examconfig.props.label.allowSpellCheckDictionary.en-GB=English (United Kingdom) (en-GB)
|
||||
|
@ -702,8 +804,8 @@ sebserver.examconfig.props.group.newBrowserWindow=Links requesting to be opened
|
|||
sebserver.examconfig.props.label.newBrowserWindowByLinkPolicy.0=get generally blocked
|
||||
sebserver.examconfig.props.label.newBrowserWindowByLinkPolicy.1=open in same window
|
||||
sebserver.examconfig.props.label.newBrowserWindowByLinkPolicy.2=open in new window
|
||||
sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign=Block when directing</br>to a different server
|
||||
sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign.tooltip=USE WITH CARE: Hyperlinks invoked by JavaScript/plug-ins</br> which direct to a different host than the one of the current main page will be ignored.
|
||||
sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign=Block when directing<br/>to a different server
|
||||
sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign.tooltip=USE WITH CARE: Hyperlinks invoked by JavaScript/plug-ins<br/> which direct to a different host than the one of the current main page will be ignored.
|
||||
|
||||
sebserver.examconfig.props.group.newwinsize=New browser window size and position
|
||||
sebserver.examconfig.props.label.newBrowserWindowByLinkWidth=Width
|
||||
|
@ -717,19 +819,19 @@ sebserver.examconfig.props.label.newBrowserWindowByLinkPositioning.2=Right
|
|||
|
||||
sebserver.examconfig.props.group.browserSecurity=Browser security
|
||||
sebserver.examconfig.props.label.enablePlugIns=Enable plug-ins (Win: only Flash)
|
||||
sebserver.examconfig.props.label.enablePlugIns.tooltip=Enables web plugins (Mac) or just Flash (Win).</br> For security reasons it\'s recommended to disable this option if you don\'t use any plugin/Flash content.
|
||||
sebserver.examconfig.props.label.enablePlugIns.tooltip=Enables web plugins (Mac) or just Flash (Win).<br/> For security reasons it\'s recommended to disable this option if you don\'t use any plugin/Flash content.
|
||||
sebserver.examconfig.props.label.enableJavaScript=Enable JavaScript
|
||||
sebserver.examconfig.props.label.enableJavaScript.tooltip=Enables JavaScript.</br> Please note that most modern web-sites need JavaScript for full functionality.
|
||||
sebserver.examconfig.props.label.enableJavaScript.tooltip=Enables JavaScript.<br/> Please note that most modern web-sites need JavaScript for full functionality.
|
||||
sebserver.examconfig.props.label.enableJava=Enable Java
|
||||
sebserver.examconfig.props.label.enableJava.tooltip=Enables Java applets.</br> Note: Only applets with the highest Java security level will run in SEB.
|
||||
sebserver.examconfig.props.label.enableJava.tooltip=Enables Java applets.<br/> Note: Only applets with the highest Java security level will run in SEB.
|
||||
sebserver.examconfig.props.label.blockPopUpWindows=Block pop-up windows
|
||||
sebserver.examconfig.props.label.blockPopUpWindows.tooltip=Disables pop-up windows</br> (often advertisement) opened by JavaScript without an user action such as a button click.
|
||||
sebserver.examconfig.props.label.blockPopUpWindows.tooltip=Disables pop-up windows<br/> (often advertisement) opened by JavaScript without an user action such as a button click.
|
||||
sebserver.examconfig.props.label.allowVideoCapture=Allow video capture (webcam)
|
||||
sebserver.examconfig.props.label.allowVideoCapture.tooltip=Allow web applications to access camera
|
||||
sebserver.examconfig.props.label.allowAudioCapture=Allow audio capture (microphone)
|
||||
sebserver.examconfig.props.label.allowAudioCapture.tooltip=Allow web applications to access microphone
|
||||
sebserver.examconfig.props.label.allowBrowsingBackForward=Allow navigating back/forward in exam
|
||||
sebserver.examconfig.props.label.allowBrowsingBackForward.tooltip=Disabling browsing to previously visited pages may increase security,</br> because browsing back might allow to leave an exam
|
||||
sebserver.examconfig.props.label.allowBrowsingBackForward.tooltip=Disabling browsing to previously visited pages may increase security,<br/> because browsing back might allow to leave an exam
|
||||
sebserver.examconfig.props.label.newBrowserWindowNavigation=Allow navigating in additional windows
|
||||
sebserver.examconfig.props.label.browserWindowAllowReload=Allow reload exam
|
||||
sebserver.examconfig.props.label.browserWindowAllowReload.tooltip=Allow reload in the exam window with F5 reload button (if displayed)
|
||||
|
@ -742,7 +844,7 @@ sebserver.examconfig.props.label.newBrowserWindowShowReloadWarning.tooltip=User
|
|||
sebserver.examconfig.props.label.removeBrowserProfile=Remove profile (Win)
|
||||
sebserver.examconfig.props.label.removeBrowserProfile.tooltip=Remove XULRunner browser profile (containing caches and also local storage) when quitting SEB
|
||||
sebserver.examconfig.props.label.removeLocalStorage=Disable local storage (Mac)
|
||||
sebserver.examconfig.props.label.removeLocalStorage.tooltip=If your web application uses local storage, you have to be sure data is saved encrypted</br> and removed when no longer needed as SEB doesn't remove local storage
|
||||
sebserver.examconfig.props.label.removeLocalStorage.tooltip=If your web application uses local storage, you have to be sure data is saved encrypted<br/> and removed when no longer needed as SEB doesn't remove local storage
|
||||
|
||||
sebserver.examconfig.props.label.browserUserAgent=Suffix to be added to any user agent
|
||||
sebserver.examconfig.props.group.userAgentDesktop=User agent for desktop mode
|
||||
|
@ -750,7 +852,7 @@ sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.0=Desktop defaul
|
|||
sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.0.tooltip=Zoom whole web pages using Ctrl-Mousewheel (Win)
|
||||
sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.1=Custom
|
||||
sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.1.tooltip=Zoom only text on web pages using Ctrl-Mousewheel (Win)
|
||||
sebserver.examconfig.props.label.browserUserAgentWinDesktopModeCustom.tooltip=Custom desktop user agent string</br>(SEB appends its version number automatically)
|
||||
sebserver.examconfig.props.label.browserUserAgentWinDesktopModeCustom.tooltip=Custom desktop user agent string<br/>(SEB appends its version number automatically)
|
||||
|
||||
sebserver.examconfig.props.group.userAgentTouch=User agent for touch/table mode
|
||||
sebserver.examconfig.props.label.browserUserAgentWinTouchMode.0=Touch default
|
||||
|
@ -764,28 +866,28 @@ sebserver.examconfig.props.label.browserUserAgentMac.1=Custom
|
|||
sebserver.examconfig.props.label.browserUserAgentMac.1.tooltip=Zoom only text on web pages using Ctrl-Mousewheel (Win)
|
||||
|
||||
sebserver.examconfig.props.label.enableSebBrowser=Enable SEB with browser window
|
||||
sebserver.examconfig.props.label.enableSebBrowser.tooltip=Disable this to start another application in kiosk mode</br>(for example a virtual desktop infrastructure client)
|
||||
sebserver.examconfig.props.label.enableSebBrowser.tooltip=Disable this to start another application in kiosk mode<br/>(for example a virtual desktop infrastructure client)
|
||||
sebserver.examconfig.props.label.browserWindowTitleSuffix=Suffix to be added to every browser window
|
||||
|
||||
sebserver.examconfig.props.label.allowDownUploads=Allow downloading and uploading files (Mac)
|
||||
sebserver.examconfig.props.label.allowDownUpload.tooltip=Usually to be used with permitted third party applications</br> for which you want to provide files to be down-loaded.
|
||||
sebserver.examconfig.props.label.allowDownUpload.tooltip=Usually to be used with permitted third party applications<br/> for which you want to provide files to be down-loaded.
|
||||
sebserver.examconfig.props.label.downloadDirectoryWin=Download directory (Win)
|
||||
sebserver.examconfig.props.label.downloadDirectoryOSX=Download directory (Mac)
|
||||
sebserver.examconfig.props.label.openDownloads=Open files after downloading (Mac)
|
||||
sebserver.examconfig.props.label.chooseFileToUploadPolicy=Choose file to upload (Mac)
|
||||
sebserver.examconfig.props.label.chooseFileToUploadPolicy.tooltip=SEB can let users choose the file to upload or automatically use the same file which was down-loaded before.</br>If not found, a file requester or an error is presented depending on this setting.
|
||||
sebserver.examconfig.props.label.chooseFileToUploadPolicy.tooltip=SEB can let users choose the file to upload or automatically use the same file which was down-loaded before.<br/>If not found, a file requester or an error is presented depending on this setting.
|
||||
sebserver.examconfig.props.label.chooseFileToUploadPolicy.0=manually with file requester
|
||||
sebserver.examconfig.props.label.chooseFileToUploadPolicy.1=by attempting to upload the same file downloaded before
|
||||
sebserver.examconfig.props.label.chooseFileToUploadPolicy.2=by only allowing to upload the same file downloaded before
|
||||
sebserver.examconfig.props.label.downloadPDFFiles=Download and open PDF files instead of displaying them inline (Mac)
|
||||
sebserver.examconfig.props.label.downloadPDFFiles.tooltip=PDF files will not be displayed by SEB but downloaded and openend (if "Open files after downloading" is active!)</br> by the application set in Finder (usually Preview or Adobe Acrobat).
|
||||
sebserver.examconfig.props.label.downloadPDFFiles.tooltip=PDF files will not be displayed by SEB but downloaded and openend (if "Open files after downloading" is active!)<br/> by the application set in Finder (usually Preview or Adobe Acrobat).
|
||||
sebserver.examconfig.props.label.allowPDFPlugIn=Allow using Acrobat Reader PDF plugin (insecure! Mac only)
|
||||
sebserver.examconfig.props.label.allowPDFPlugIn.tooltip=The Adobe Acrobat Reader browser plugin should only be used on secured managed Mac computers,</br> at it allows limited access the file system and unlimited to cloud services
|
||||
sebserver.examconfig.props.label.allowPDFPlugIn.tooltip=The Adobe Acrobat Reader browser plugin should only be used on secured managed Mac computers,<br/> at it allows limited access the file system and unlimited to cloud services
|
||||
sebserver.examconfig.props.label.downloadAndOpenSebConfig=Download and open SEB Config Files
|
||||
sebserver.examconfig.props.label.downloadAndOpenSebConfig.tooltip=Download and open .seb config files regardless if downloading and opening other file types is allowed.
|
||||
|
||||
sebserver.examconfig.props.group.quitLink=Link to quit SEB after exam
|
||||
sebserver.examconfig.props.label.quitURL=Place this quit link to the 'feedback' page displayed after an exam was successfully finished.</br> Clicking that link will quit SEB without having to enter the quit password.
|
||||
sebserver.examconfig.props.label.quitURL=Place this quit link to the 'feedback' page displayed after an exam was successfully finished.<br/> Clicking that link will quit SEB without having to enter the quit password.
|
||||
sebserver.examconfig.props.label.quitURLConfirm=Ask user to confirm quitting
|
||||
|
||||
sebserver.examconfig.props.group.backToStart=Back to Start Button
|
||||
|
@ -798,7 +900,7 @@ sebserver.examconfig.props.label.restartExamPasswordProtected=Protect back to st
|
|||
sebserver.examconfig.props.label.restartExamPasswordProtected.tooltip=The quit/restart password (if set) must be entered when the back to start button was pressed.
|
||||
|
||||
sebserver.examconfig.props.label.allowSwitchToApplications=Allow switching to third party application (Mac)
|
||||
sebserver.examconfig.props.label.allowSwitchToApplications.tooltip=Decreases security of the kiosk mode by allowing process switcher (Cmd+Tab).</br> The blacked out background of SEB also doesn't cover some alerts and modal windows in this mode.
|
||||
sebserver.examconfig.props.label.allowSwitchToApplications.tooltip=Decreases security of the kiosk mode by allowing process switcher (Cmd+Tab).<br/> The blacked out background of SEB also doesn't cover some alerts and modal windows in this mode.
|
||||
sebserver.examconfig.props.label.allowFlashFullscreen=Allow Flash to switch to fullscreen mode (Mac)
|
||||
sebserver.examconfig.props.label.permittedProcesses.add.tooltip=Add permitted process
|
||||
sebserver.examconfig.props.label.permittedProcesses.remove.tooltip=Remove selected permitted process
|
||||
|
@ -811,11 +913,11 @@ sebserver.examconfig.props.label.permittedProcesses.os.tooltip=Indicates on whic
|
|||
sebserver.examconfig.props.label.permittedProcesses.os.0=OS X
|
||||
sebserver.examconfig.props.label.permittedProcesses.os.1=Win
|
||||
sebserver.examconfig.props.label.permittedProcesses.title=Title
|
||||
sebserver.examconfig.props.label.permittedProcesses.title.tooltip=Application title which is displayed in the application chooser.</br> Background processes don't have a title, because they can't be selected by users.
|
||||
sebserver.examconfig.props.label.permittedProcesses.title.tooltip=Application title which is displayed in the application chooser.<br/> Background processes don't have a title, because they can't be selected by users.
|
||||
sebserver.examconfig.props.label.permittedProcesses.description=Description
|
||||
sebserver.examconfig.props.label.permittedProcesses.description.tooltip=Optional, should explain what kind of process this is,</br> because this might not be obvious only from the executable's name.
|
||||
sebserver.examconfig.props.label.permittedProcesses.description.tooltip=Optional, should explain what kind of process this is,<br/> because this might not be obvious only from the executable's name.
|
||||
sebserver.examconfig.props.label.permittedProcesses.executable=Executable
|
||||
sebserver.examconfig.props.label.permittedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path,</br> only the filename of the exe file (like calc.exe).
|
||||
sebserver.examconfig.props.label.permittedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path,<br/> only the filename of the exe file (like calc.exe).
|
||||
sebserver.examconfig.props.label.permittedProcesses.originalName=Original Name
|
||||
sebserver.examconfig.props.label.permittedProcesses.allowedExecutables=Window handling process
|
||||
sebserver.examconfig.props.label.permittedProcesses.path=Path
|
||||
|
@ -825,13 +927,13 @@ sebserver.examconfig.props.label.permittedProcesses.arguments.argument=Argument
|
|||
sebserver.examconfig.props.label.permittedProcesses.arguments.addAction=Add new argument
|
||||
sebserver.examconfig.props.label.permittedProcesses.arguments.removeAction=Remove this argument
|
||||
sebserver.examconfig.props.label.permittedProcesses.identifier=Identifier
|
||||
sebserver.examconfig.props.label.permittedProcesses.identifier.tooltip=(Sub) string in the title of the main window of a tricky third party application (Java, Acrobat etc.).</br> Mac OS X: Bundle identifier of the process in reverse domain notation.
|
||||
sebserver.examconfig.props.label.permittedProcesses.identifier.tooltip=(Sub) string in the title of the main window of a tricky third party application (Java, Acrobat etc.).<br/> Mac OS X: Bundle identifier of the process in reverse domain notation.
|
||||
sebserver.examconfig.props.label.permittedProcesses.iconInTaskbar=Icon in taskbar
|
||||
sebserver.examconfig.props.label.permittedProcesses.iconInTaskbar.tooltip=Show icon of permitted application in task bar</br> (not possible when 'run in background' is enabled).
|
||||
sebserver.examconfig.props.label.permittedProcesses.iconInTaskbar.tooltip=Show icon of permitted application in task bar<br/> (not possible when 'run in background' is enabled).
|
||||
sebserver.examconfig.props.label.permittedProcesses.autostart=Autostart
|
||||
sebserver.examconfig.props.label.permittedProcesses.autostart.tooltip=Start the process automatically together with SEB.
|
||||
sebserver.examconfig.props.label.permittedProcesses.runInBackground=Allow running in background
|
||||
sebserver.examconfig.props.label.permittedProcesses.runInBackground.tooltip=Allow the permitted process to already be running when SEB starts.</br> Such a process can't have an icon in the task bar.
|
||||
sebserver.examconfig.props.label.permittedProcesses.runInBackground.tooltip=Allow the permitted process to already be running when SEB starts.<br/> Such a process can't have an icon in the task bar.
|
||||
sebserver.examconfig.props.label.permittedProcesses.allowUserToChooseApp=Allow user to select location of application
|
||||
sebserver.examconfig.props.label.permittedProcesses.strongKill=Force quit (risk of data loss)
|
||||
sebserver.examconfig.props.label.permittedProcesses.strongKill.tooltip=Terminate process in a not-nice way, which may cause data loss if the application had unsaved data
|
||||
|
@ -846,15 +948,15 @@ sebserver.examconfig.props.label.prohibitedProcesses.os=OS
|
|||
sebserver.examconfig.props.label.prohibitedProcesses.os.0=OS X
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.os.1=Win
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.description=Description
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.description.tooltip=Optional, to explain what kind of process this is,</br> because this might not be obvious only from the executable's name.
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.description.tooltip=Optional, to explain what kind of process this is,<br/> because this might not be obvious only from the executable's name.
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.executable=Executable
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path,</br> only the filename of the exe file (like calc.exe).
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path,<br/> only the filename of the exe file (like calc.exe).
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.originalName=Original Name
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.originalName.tooltip=Original file name (optional)
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.identifier=Identifier
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.identifier.tooltip=Title of the main window of a Java third party application.</br> Mac OS X: Bundle identifier of the process in reverse domain notation.
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.identifier.tooltip=Title of the main window of a Java third party application.<br/> Mac OS X: Bundle identifier of the process in reverse domain notation.
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.strongKill=Force quit (risk of data loss)
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.strongKill.tooltip=Terminate process in a not-nice way,</br> which may cause data loss if the application had unsaved data
|
||||
sebserver.examconfig.props.label.prohibitedProcesses.strongKill.tooltip=Terminate process in a not-nice way,<br/> which may cause data loss if the application had unsaved data
|
||||
|
||||
sebserver.examconfig.props.group.urlFilter=Filter
|
||||
sebserver.examconfig.props.label.URLFilterEnable=Activate URL Filtering
|
||||
|
@ -1014,7 +1116,7 @@ sebserver.examconfig.props.label.insideSebEnableLogOff.tooltip=Activates the but
|
|||
sebserver.examconfig.props.label.insideSebEnableShutDown=Enable Shut down
|
||||
sebserver.examconfig.props.label.insideSebEnableShutDown.tooltip=Activates the button "Shutdown"
|
||||
sebserver.examconfig.props.label.insideSebEnableEaseOfAccess=Enable Ease of Access
|
||||
sebserver.examconfig.props.label.insideSebEnableEaseOfAccess.tooltip=Shows options when the button "Ease of Access" in the lower left corner is clicked,</br>which offers help e.g. to visually or aurally handicapped persons, like the Magnifier Glass.
|
||||
sebserver.examconfig.props.label.insideSebEnableEaseOfAccess.tooltip=Shows options when the button "Ease of Access" in the lower left corner is clicked,<br/>which offers help e.g. to visually or aurally handicapped persons, like the Magnifier Glass.
|
||||
sebserver.examconfig.props.label.insideSebEnableVmWareClientShade=Enable VMware Client Shade
|
||||
sebserver.examconfig.props.label.insideSebEnableVmWareClientShade.tooltip=Activates the "Shade" bar at the upper edge of a virtual desktop, if existent. If you're not using VMware, this setting doesn't have any effect.
|
||||
sebserver.examconfig.props.label.insideSebEnableNetworkConnectionSelector=Enable network connection selector
|
||||
|
@ -1103,13 +1205,13 @@ sebserver.configtemplate.attr.type.COMPOSITE_TABLE=Table
|
|||
sebserver.configtemplate.attrs.list.title=Configuration Attributes
|
||||
sebserver.configtemplate.attrs.list.title.tooltip=Table of all SEB exam configuration attributes of this template
|
||||
sebserver.configtemplate.attrs.list.name=Name
|
||||
sebserver.configtemplate.attrs.list.name.tooltip=The technical name of the SEB exam configuration attribute with the display name in brackets if available.</br></br>{0}
|
||||
sebserver.configtemplate.attrs.list.name.tooltip=The technical name of the SEB exam configuration attribute with the display name in brackets if available.<br/><br/>{0}
|
||||
sebserver.configtemplate.attrs.list.view=View
|
||||
sebserver.configtemplate.attrs.list.view.tooltip=The view/tab where the SEB exam configuration attribute belongs to.</br></br>{0}
|
||||
sebserver.configtemplate.attrs.list.view.tooltip=The view/tab where the SEB exam configuration attribute belongs to.<br/><br/>{0}
|
||||
sebserver.configtemplate.attrs.list.group=Group
|
||||
sebserver.configtemplate.attrs.list.group.tooltip=The group on the view/tab where the SEB exam configuration attribute belongs to.</br></br>{0}
|
||||
sebserver.configtemplate.attrs.list.group.tooltip=The group on the view/tab where the SEB exam configuration attribute belongs to.<br/><br/>{0}
|
||||
sebserver.configtemplate.attrs.list.type=Type
|
||||
sebserver.configtemplate.attrs.list.type.tooltip=The type of the SEB exam configuration attribute.</br></br>{0}
|
||||
sebserver.configtemplate.attrs.list.type.tooltip=The type of the SEB exam configuration attribute.<br/><br/>{0}
|
||||
|
||||
sebserver.configtemplate.attr.list.actions=
|
||||
sebserver.configtemplate.attr.list.actions.modify=Edit Attribute
|
||||
|
@ -1149,17 +1251,31 @@ sebserver.monitoring.exam.info.pleaseSelect=Please select first an Exam from the
|
|||
sebserver.monitoring.exam.list.empty=There are currently no running exams
|
||||
|
||||
sebserver.monitoring.exam.list.column.name=Name
|
||||
sebserver.monitoring.exam.list.column.name.tooltip=The name of the exam.<br/><br/>Use the filter above to narrow down a specific exam name.<br/>{0}
|
||||
sebserver.monitoring.exam.list.column.type=Type
|
||||
sebserver.monitoring.exam.list.column.type.tooltip=The type of the exam.<br/><br/>Use the filter above to set a specific exam type.<br/>{0}
|
||||
sebserver.monitoring.exam.list.column.startTime=Start Time {0}
|
||||
sebserver.monitoring.exam.list.column.startTime.tooltip=The start date and time of the exam.<br/><br/>{0}
|
||||
sebserver.monitoring.exam.list.column.endTime=End Time {0}
|
||||
sebserver.monitoring.exam.list.column.endTime.tooltip=The end date and time of the exam.<br/><br/>{0}
|
||||
|
||||
sebserver.monitoring.exam=Monitoring Exam: {0}
|
||||
|
||||
sebserver.monitoring.connection.list.column.id=Client Identifier
|
||||
sebserver.monitoring.connection.list.column.id=User Name or Session
|
||||
sebserver.monitoring.connection.list.column.id.tooltip=The user session identifier or username sent by the SEB client after LMS login.
|
||||
sebserver.monitoring.connection.list.column.address=IP Address
|
||||
sebserver.monitoring.connection.list.column.address.tooltip=The IP address from the host the SEB client is connecting to the SEB Server.
|
||||
sebserver.monitoring.connection.list.column.status=Status
|
||||
sebserver.monitoring.connection.list.column.examname=Exam
|
||||
sebserver.monitoring.connection.list.column.vdiAddress=IP Address (VDI)
|
||||
sebserver.monitoring.connection.list.column.status.tooltip=The current connection status
|
||||
|
||||
sebserver.monitoring.connection.form.id=User Name or Session
|
||||
sebserver.monitoring.connection.form.id.tooltip=The user session identifier or username sent by the SEB client after LMS login.
|
||||
sebserver.monitoring.connection.form.address=IP Address
|
||||
sebserver.monitoring.connection.form.address.tooltip=The IP address from the host the SEB client is connecting to the SEB Server.
|
||||
sebserver.monitoring.connection.form.status=Status
|
||||
sebserver.monitoring.connection.form.status.tooltip=The current connection status
|
||||
sebserver.monitoring.connection.form.exam=Exam
|
||||
sebserver.monitoring.connection.form.exam.tooltip=The exam name
|
||||
|
||||
sebserver.monitoring.exam.connection.emptySelection=Please select first a Connection from the list
|
||||
sebserver.monitoring.exam.connection.emptySelection.active=Please select first an active Connection from the list
|
||||
|
@ -1186,10 +1302,15 @@ sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined
|
|||
sebserver.monitoring.exam.connection.eventlist.title=Events
|
||||
sebserver.monitoring.exam.connection.eventlist.empty=No event found
|
||||
sebserver.monitoring.exam.connection.eventlist.type=Event Type
|
||||
sebserver.monitoring.exam.connection.eventlist.type.tooltip=The type of the log event.<br/><br/>Use the filter above to set a specific event type.<br/>{0}
|
||||
sebserver.monitoring.exam.connection.eventlist.clienttime=Client Time {0}
|
||||
sebserver.monitoring.exam.connection.eventlist.clienttime.tooltip=The time the SEB client has sent within the log event.<br/><br/>{0}
|
||||
sebserver.monitoring.exam.connection.eventlist.servertime=Server Time {0}
|
||||
sebserver.monitoring.exam.connection.eventlist.servertime.tooltip=The exact time (UTC) the SEB Server has received the log event.<br/><br/>{0}
|
||||
sebserver.monitoring.exam.connection.eventlist.value=Value
|
||||
sebserver.monitoring.exam.connection.eventlist.value.tooltip=The value of the log event.<br/><br/>{0}
|
||||
sebserver.monitoring.exam.connection.eventlist.text=Text
|
||||
sebserver.monitoring.exam.connection.eventlist.text.tooltip=The text of the log event.<br/><br/>{0}
|
||||
|
||||
sebserver.monitoring.exam.connection.event.type.UNKNOWN=Unknown
|
||||
sebserver.monitoring.exam.connection.event.type.DEBUG_LOG=Debug
|
||||
|
@ -1218,15 +1339,15 @@ sebserver.logs.activity.seblogs.details=Show Details
|
|||
|
||||
sebserver.userlogs.list.title=User Activity Logs
|
||||
sebserver.userlogs.list.column.institution=Institution
|
||||
sebserver.userlogs.list.column.institution.tooltip=The institution of the user activity log.</br></br>Use the filter above to specify the institution.</br>{0}
|
||||
sebserver.userlogs.list.column.institution.tooltip=The institution of the user activity log.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.userlogs.list.column.user=User
|
||||
sebserver.userlogs.list.column.user.tooltip=The user account of the user activity log.</br></br>Use the filter above to specify a user account.</br>{0}
|
||||
sebserver.userlogs.list.column.user.tooltip=The user account of the user activity log.<br/><br/>Use the filter above to specify a user account.<br/>{0}
|
||||
sebserver.userlogs.list.column.dateTime=Date {0}
|
||||
sebserver.userlogs.list.column.dateTime.tooltip=The date when the user activity log happened.</br></br>Use the filter above to specify a from- and to-date range.</br>{0}
|
||||
sebserver.userlogs.list.column.dateTime.tooltip=The date when the user activity log happened.<br/><br/>Use the filter above to specify a from- and to-date range.<br/>{0}
|
||||
sebserver.userlogs.list.column.activityType=User Activity
|
||||
sebserver.userlogs.list.column.activityType.tooltip=The type of the user activity.</br></br>Use the filter above to specify a activity type.</br>{0}
|
||||
sebserver.userlogs.list.column.activityType.tooltip=The type of the user activity.<br/><br/>Use the filter above to specify a activity type.<br/>{0}
|
||||
sebserver.userlogs.list.column.entityType=Domain Type
|
||||
sebserver.userlogs.list.column.entityType.tooltip=The domain type of the user activity.</br></br>Use the filter above to specify a domain type.</br>{0}
|
||||
sebserver.userlogs.list.column.entityType.tooltip=The domain type of the user activity.<br/><br/>Use the filter above to specify a domain type.<br/>{0}
|
||||
sebserver.userlogs.list.column.entityId=Entity-ID
|
||||
sebserver.userlogs.list.column.message=Message
|
||||
|
||||
|
@ -1240,13 +1361,19 @@ sebserver.seblogs.list.title=SEB Client Logs
|
|||
sebserver.seblogs.list.actions=
|
||||
sebserver.seblogs.list.empty=No SEB client logs has been found. Please adapt or clear the filter
|
||||
|
||||
sebserver.seblogs.info.pleaseSelect=Please select first a SEB Client Log from the list
|
||||
sebserver.seblogs.info.pleaseSelect=Please select first a SEB client Log from the list
|
||||
sebserver.seblogs.list.column.institution=Institution
|
||||
sebserver.seblogs.list.column.institution.tooltip=The institution where the exam belongs to.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
sebserver.seblogs.list.column.exam=Exam
|
||||
sebserver.seblogs.list.column.exam.tooltip=The exam of the SEB client logs.<br/><br/>Use the filter above to specify an exam.<br/>{0}
|
||||
sebserver.seblogs.list.column.client-session=User Session-ID
|
||||
sebserver.seblogs.list.column.client-session.tooltip=The user or user-session identifier.<br/><br/>Use the filter above narrow down a user identifier name.<br/>{0}
|
||||
sebserver.seblogs.list.column.type=Event Type
|
||||
sebserver.seblogs.list.column.type.tooltip=The SEB client log event type.<br/><br/>Use the filter above to specify log type.<br/>{0}
|
||||
sebserver.seblogs.list.column.time=Event Time {0}
|
||||
sebserver.seblogs.list.column.time.tooltip=The SEB client log time.<br/><br/>Use the filter above to specify from- and to-date and time.<br/>{0}
|
||||
sebserver.seblogs.list.column.value=Value
|
||||
sebserver.seblogs.list.column.value.tooltip=The SEB client log value.<br/><br/>{0}
|
||||
|
||||
sebserver.seblogs.details.title=SEB Client Log Details
|
||||
sebserver.seblogs.details.event.title=Event
|
||||
|
@ -1255,19 +1382,34 @@ sebserver.seblogs.details.exam.title=Exam Details
|
|||
sebserver.seblogs.details.dateTime=Date
|
||||
|
||||
sebserver.seblogs.form.column.client-session=Session-ID
|
||||
sebserver.seblogs.form.column.client-session.tooltip=The user or user-session identifier.
|
||||
sebserver.seblogs.form.column.type=Event Type
|
||||
sebserver.seblogs.form.column.type.tooltip=The SEB client log event type.
|
||||
sebserver.seblogs.form.column.server-time=Server Time
|
||||
sebserver.seblogs.form.column.server-time.tooltip=The exact time when the SEB Server got the event log sent by an SEB client.
|
||||
sebserver.seblogs.form.column.client-time=SEB Client Time
|
||||
sebserver.seblogs.form.column.client-time.tooltip=The time that was send within the log from SEB client.
|
||||
sebserver.seblogs.form.column.value=Value
|
||||
sebserver.seblogs.form.column.value.tooltip=The SEB client log event value
|
||||
sebserver.seblogs.form.column.message=Message
|
||||
sebserver.seblogs.form.column.message.tooltip=The SEB client log message
|
||||
|
||||
sebserver.seblogs.form.column.connection.session-id=User Session-ID
|
||||
sebserver.seblogs.form.column.connection.address=SEB Client Address
|
||||
sebserver.seblogs.form.column.connection.session-id.tooltip=The user or user-session identifier.
|
||||
sebserver.seblogs.form.column.connection.address=SEB client Address
|
||||
sebserver.seblogs.form.column.connection.address.tooltip=The IP address of the SEB client
|
||||
sebserver.seblogs.form.column.connection.token=SEB Connection Token
|
||||
sebserver.seblogs.form.column.connection.token.tooltip=The connection token that was generated by the SEB Server to identify the SEB client connection.
|
||||
sebserver.seblogs.form.column.connection.status=Connection Status
|
||||
sebserver.seblogs.form.column.connection.status.tooltip=The current SEB client connection status.
|
||||
|
||||
sebserver.seblogs.form.column.exam.name=Name
|
||||
sebserver.seblogs.form.column.exam.name.tooltip=The name of the exam.
|
||||
sebserver.seblogs.form.column.exam.description=Description
|
||||
sebserver.seblogs.form.column.exam.description.tooltip=The description of the exam.
|
||||
sebserver.seblogs.form.column.exam.type=Type
|
||||
sebserver.seblogs.form.column.exam.type.tooltip=The type of the exam
|
||||
sebserver.seblogs.form.column.exam.startTime=Start Time
|
||||
sebserver.seblogs.form.column.exam.endTime=End Time
|
||||
sebserver.seblogs.form.column.exam.startTime.tooltip=The start date and time of the exam
|
||||
sebserver.seblogs.form.column.exam.endTime=End Time
|
||||
sebserver.seblogs.form.column.exam.endTime.tooltip=The end date and time of the exam
|
Loading…
Add table
Reference in a new issue