diff --git a/pom.xml b/pom.xml index 1424dbda..ccd1c323 100644 --- a/pom.xml +++ b/pom.xml @@ -289,6 +289,11 @@ org.apache.commons commons-lang3 + + org.cryptonode.jncryptor + jncryptor + 1.2.0 + diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 81f56ef8..1430ae4c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -28,15 +28,23 @@ public final class Constants { public static final String FORM_URL_ENCODED_SEPARATOR = "&"; public static final String FORM_URL_ENCODED_NAME_VALUE_SEPARATOR = "="; + public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; public static final String DEFAULT_DISPLAY_DATE_FORMAT = "MM-dd-yyy HH:mm"; public static final String TIME_ZONE_OFFSET_TAIL_FORMAT = "|ZZ"; + public static final DateTimeFormatter STANDARD_DATE_TIME_FORMATTER = DateTimeFormat + .forPattern(DEFAULT_DATE_TIME_FORMAT) + .withZoneUTC(); + /** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */ + // TODO check if this works with DEFAULT_DATE_TIME_FORMAT + @Deprecated public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat .forPattern("yyyy-MM-dd HH:mm:ss") .withZoneUTC(); /** Date-Time formatter with milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss.S */ + @Deprecated public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_MILLIS = DateTimeFormat .forPattern("yyyy-MM-dd HH:mm:ss.S") .withZoneUTC(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index e00cc9cd..132762ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -20,10 +20,10 @@ public final class API { public static final String PARAM_INSTITUTION_ID = "institutionId"; public static final String PARAM_MODEL_ID = "modelId"; public static final String PARAM_MODEL_ID_LIST = "modelIds"; + public static final String PARAM_PARENT_MODEL_ID = "parentModelId"; public static final String PARAM_ENTITY_TYPE = "entityType"; public static final String PARAM_BULK_ACTION_TYPE = "bulkActionType"; public static final String PARAM_LMS_SETUP_ID = "lmsSetupId"; - public static final String PARAM_EXAM_ID = "examId"; public static final String INSTITUTION_VAR_PATH_SEGMENT = "/{" + PARAM_INSTITUTION_ID + "}"; public static final String MODEL_ID_VAR_PATH_SEGMENT = "/{" + PARAM_MODEL_ID + "}"; @@ -54,7 +54,7 @@ public final class API { public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam"; - public static final String EXAM_INDICATOR_ENDPOINT = "/exam/indicator"; + public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; public static final String USER_ACTIVITY_LOG_ENDPOINT = "/useractivity"; @@ -75,4 +75,20 @@ public final class API { public static final String PATH_VAR_ACTIVE = MODEL_ID_VAR_PATH_SEGMENT + ACTIVE_PATH_SEGMENT; public static final String PATH_VAR_INACTIVE = MODEL_ID_VAR_PATH_SEGMENT + INACTIVE_PATH_SEGMENT; + // ************************* + // ** Exam API + // ************************* + + public static final String EXAM_API_PARAM_EXAM_ID = "examId"; + + public static final String EXAM_API_SEB_CONNECTION_TOKEN = "seb-connection-token"; + + public static final String EXAM_API_HANDSHAKE_ENDPOINT = "/handshake"; + + public static final String EXAM_API_CONFIGURATION_REQUEST_ENDPOINT = "/examconfig"; + + public static final String EXAM_API_PING_ENDPOINT = "/sebping"; + + public static final String EXAM_API_EVENT_ENDPOINT = "/sebevent"; + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java index 006ff3cb..3e401443 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java @@ -48,12 +48,12 @@ public final class Indicator implements GrantEntity { public final Long examId; @JsonProperty(INDICATOR.ATTR_NAME) - @NotNull + @NotNull(message = "indicator:name:notNull") @Size(min = 3, max = 255, message = "indicator:name:size:{min}:{max}:${validatedValue}") public final String name; @JsonProperty(INDICATOR.ATTR_TYPE) - @NotNull + @NotNull(message = "indicator:type:notNull") public final IndicatorType type; @JsonProperty(INDICATOR.ATTR_COLOR) @@ -62,12 +62,14 @@ public final class Indicator implements GrantEntity { @JsonProperty(THRESHOLD.REFERENCE_NAME) public final List thresholds; + public final String examOwner; + @JsonCreator public Indicator( @JsonProperty(INDICATOR.ATTR_ID) final Long id, @JsonProperty(EXAM.ATTR_INSTITUTION_ID) final Long institutionId, - @JsonProperty(EXAM.ATTR_OWNER) final String owner, @JsonProperty(INDICATOR.ATTR_EXAM_ID) final Long examId, + @JsonProperty(EXAM.ATTR_OWNER) final String examOwner, @JsonProperty(INDICATOR.ATTR_NAME) final String name, @JsonProperty(INDICATOR.ATTR_TYPE) final IndicatorType type, @JsonProperty(INDICATOR.ATTR_COLOR) final String defaultColor, @@ -76,6 +78,7 @@ public final class Indicator implements GrantEntity { this.id = id; this.institutionId = institutionId; this.examId = examId; + this.examOwner = examOwner; this.name = name; this.type = type; this.defaultColor = defaultColor; @@ -86,6 +89,7 @@ public final class Indicator implements GrantEntity { this.id = null; this.institutionId = exam.institutionId; this.examId = exam.id; + this.examOwner = exam.owner; this.name = postParams.getString(Domain.INDICATOR.ATTR_NAME); this.type = postParams.getEnum(Domain.INDICATOR.ATTR_TYPE, IndicatorType.class); this.defaultColor = postParams.getString(Domain.INDICATOR.ATTR_COLOR); @@ -119,7 +123,7 @@ public final class Indicator implements GrantEntity { @Override public String getOwnerId() { - return null; + return this.examOwner; } public Long getExamId() { @@ -145,14 +149,18 @@ public final class Indicator implements GrantEntity { + this.defaultColor + ", thresholds=" + this.thresholds + "]"; } + public static Indicator createNew(final Long institutionId, final Exam exam) { + return new Indicator(null, institutionId, exam.id, exam.owner, null, null, null, null); + } + public static final class Threshold { - @JsonProperty(THRESHOLD.ATTR_ID) - public final Long id; - - @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) - @NotNull - public final Long indicatorId; +// @JsonProperty(THRESHOLD.ATTR_ID) +// public final Long id; +// +// @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) +// @NotNull +// public final Long indicatorId; @JsonProperty(THRESHOLD.ATTR_VALUE) @NotNull @@ -163,24 +171,24 @@ public final class Indicator implements GrantEntity { @JsonCreator public Threshold( - @JsonProperty(THRESHOLD.ATTR_ID) final Long id, - @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) final Long indicatorId, +// @JsonProperty(THRESHOLD.ATTR_ID) final Long id, +// @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) final Long indicatorId, @JsonProperty(THRESHOLD.ATTR_VALUE) final Double value, @JsonProperty(THRESHOLD.ATTR_COLOR) final String color) { - this.id = id; - this.indicatorId = indicatorId; +// this.id = id; +// this.indicatorId = indicatorId; this.value = value; this.color = color; } - public Long getId() { - return this.id; - } - - public Long getIndicatorId() { - return this.indicatorId; - } +// public Long getId() { +// return this.id; +// } +// +// public Long getIndicatorId() { +// return this.indicatorId; +// } public Double getValue() { return this.value; @@ -190,12 +198,12 @@ public final class Indicator implements GrantEntity { return this.color; } - @Override - public String toString() { - return "Threshold [id=" + this.id + ", indicatorId=" + this.indicatorId + ", value=" + this.value - + ", color=" + this.color - + "]"; - } +// @Override +// public String toString() { +// return "Threshold [id=" + this.id + ", indicatorId=" + this.indicatorId + ", value=" + this.value +// + ", color=" + this.color +// + "]"; +// } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java index 2148b904..06e75a3d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java @@ -105,12 +105,16 @@ public final class QuizData implements GrantEntity { this.lmsType = lmsType; this.name = name; this.description = description; - this.startTime = LocalDateTime - .parse(startTime, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS) - .toDateTime(DateTimeZone.UTC); - this.endTime = LocalDateTime - .parse(endTime, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS) - .toDateTime(DateTimeZone.UTC); + 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; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/PingResponse.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/PingResponse.java new file mode 100644 index 00000000..0d2611a9 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/PingResponse.java @@ -0,0 +1,13 @@ +/* + * 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.gbl.model.seb; + +public class PingResponse { + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/RunningExams.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/RunningExams.java new file mode 100644 index 00000000..d19e6f1b --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/RunningExams.java @@ -0,0 +1,13 @@ +/* + * 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.gbl.model.seb; + +public class RunningExams { + // TODO +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index e5cf3f4f..db1e9f43 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.content; import java.util.function.BooleanSupplier; import org.apache.commons.lang3.BooleanUtils; +import org.apache.tomcat.util.buf.StringUtils; import org.eclipse.swt.widgets.Composite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +37,7 @@ 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.PageUtils; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.action.Action; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; @@ -43,7 +45,6 @@ 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.exam.SaveExam; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; @@ -71,6 +72,8 @@ public class ExamForm implements TemplateComposer { new LocTextKey("sebserver.exam.indicator.list.column.name"); private final static LocTextKey thresholdColumnKey = new LocTextKey("sebserver.exam.indicator.list.column.thresholds"); + private final static LocTextKey emptySelectionTextKey = + new LocTextKey("sebserver.exam.indicator.list.pleaseSelect"); protected ExamForm( final PageFormService pageFormService, @@ -124,11 +127,6 @@ public class ExamForm implements TemplateComposer { final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam); final boolean writeGrant = userGrantCheck.w(); final boolean modifyGrant = userGrantCheck.m(); - final boolean institutionActive = restService.getBuilder(GetInstitution.class) - .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.getInstitutionId())) - .call() - .map(inst -> inst.active) - .getOr(false); // The Exam form final FormHandle formHandle = this.pageFormService.getBuilder( @@ -140,7 +138,7 @@ public class ExamForm implements TemplateComposer { .putStaticValue( Domain.EXAM.ATTR_INSTITUTION_ID, String.valueOf(exam.getInstitutionId())) - .putStaticValueIf(isNew, + .putStaticValue( Domain.EXAM.ATTR_OWNER, user.uuid) .putStaticValueIf(isNotNew, @@ -192,11 +190,48 @@ public class ExamForm implements TemplateComposer { "sebserver.exam.form.type", String.valueOf(exam.type), this.resourceService::examTypeResources)) + .addField(FormBuilder.multiComboSelection( + Domain.EXAM.ATTR_SUPPORTER, + "sebserver.exam.form.supporter", + StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR), + this.resourceService::examSupporterResources) + .withCondition(isNotNew)) .buildFor(importFromQuizData ? restService.getRestCall(ImportAsExam.class) : restService.getRestCall(SaveExam.class)); + // propagate content actions to action-pane + formContext.clearEntityKeys() + .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA) + + .createAction(ActionDefinition.EXAM_MODIFY) + .withEntityKey(entityKey) + .publishIf(() -> modifyGrant && readonly) + + .createAction(ActionDefinition.EXAM_SAVE) + .withExec(formHandle::processFormSave) + .publishIf(() -> !readonly && modifyGrant) + + .createAction(ActionDefinition.EXAM_CANCEL_MODIFY) + .withEntityKey(entityKey) + .withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData)) + .withExec(ExamForm::cancelModify) + .withConfirm("sebserver.overall.action.modify.cancel.confirm") + .publishIf(() -> !readonly) + + .createAction(ActionDefinition.EXAM_DEACTIVATE) + .withEntityKey(entityKey) + .withExec(restService::activation) + .withConfirm(PageUtils.confirmDeactivation(exam, restService)) + .publishIf(() -> writeGrant && readonly && exam.isActive()) + + .createAction(ActionDefinition.EXAM_ACTIVATE) + .withEntityKey(entityKey) + .withExec(restService::activation) + .publishIf(() -> writeGrant && readonly && !exam.isActive()); + + // additional data in read-only view if (readonly) { // List of Indicators @@ -227,24 +262,25 @@ public class ExamForm implements TemplateComposer { .compose(content); + formContext.clearEntityKeys() + .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA) + + .createAction(ActionDefinition.EXAM_INDICATOR_NEW) + .withParentEntityKey(entityKey) + .publishIf(() -> modifyGrant) + + .createAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST) + .withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey) + .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent()) + + .createAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST) + .withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey) + .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent()); + // TODO List of attached SEB Configurations } - // propagate content actions to action-pane - formContext.clearEntityKeys() - .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA) - - .createAction(ActionDefinition.EXAM_SAVE) - .withExec(formHandle::processFormSave) - .publishIf(() -> !readonly && modifyGrant) - - .createAction(ActionDefinition.EXAM_CANCEL_MODIFY) - .withEntityKey(entityKey) - .withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData)) - .withExec(ExamForm::cancelModify) - .withConfirm("sebserver.overall.action.modify.cancel.confirm") - .publishIf(() -> !readonly); } private Result getExistingExam(final EntityKey entityKey, final RestService restService) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java index 250ec71d..48236a6c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java @@ -10,11 +10,7 @@ package ch.ethz.seb.sebserver.gui.content; import java.util.function.Function; -import org.eclipse.swt.SWT; -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 org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -32,7 +28,6 @@ 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.ModalInputDialog; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.action.Action; @@ -136,9 +131,9 @@ public class ExamList implements TemplateComposer { final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM); pageContext.clearEntityKeys() - .createAction(ActionDefinition.TEST_ACTION) - .withExec(this::testModalInput) - .publish() +// .createAction(ActionDefinition.TEST_ACTION) +// .withExec(this::testModalInput) +// .publish() .createAction(ActionDefinition.EXAM_IMPORT) .publishIf(userGrant::im) @@ -172,29 +167,29 @@ public class ExamList implements TemplateComposer { .getText("sebserver.exam.type." + exam.type.name()); } - private Action testModalInput(final Action action) { - final ModalInputDialog dialog = new ModalInputDialog<>( - action.pageContext().getParent().getShell(), - this.widgetFactory); - - dialog.open( - "Test Input Dialog", - action.pageContext(), - value -> { - System.out.println("********************** value: " + value); - }, - pc -> { - final Composite parent = pc.getParent(); - final Label label = new Label(parent, SWT.NONE); - label.setText("Please Enter:"); - label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - final Text text = new Text(parent, SWT.LEFT | SWT.BORDER); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return () -> text.getText(); - }); - - return action; - } +// private Action testModalInput(final Action action) { +// final ModalInputDialog dialog = new ModalInputDialog<>( +// action.pageContext().getParent().getShell(), +// this.widgetFactory); +// +// dialog.open( +// "Test Input Dialog", +// action.pageContext(), +// value -> { +// System.out.println("********************** value: " + value); +// }, +// pc -> { +// final Composite parent = pc.getParent(); +// final Label label = new Label(parent, SWT.NONE); +// label.setText("Please Enter:"); +// label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); +// +// final Text text = new Text(parent, SWT.LEFT | SWT.BORDER); +// text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); +// return () -> text.getText(); +// }); +// +// return action; +// } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java index 568920db..60b341c3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java @@ -8,20 +8,47 @@ package ch.ethz.seb.sebserver.gui.content; +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; +import ch.ethz.seb.sebserver.gbl.api.API; +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.user.UserInfo; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +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.form.PageFormService; 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.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; +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.service.remote.webservice.auth.CurrentUser; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @Lazy @Component @GuiProfile public class IndicatorForm implements TemplateComposer { + private static final Logger log = LoggerFactory.getLogger(IndicatorForm.class); + + private final static LocTextKey listTitleKey = + new LocTextKey("sebserver.exam.indicator.thresholds.list.title"); + private final PageFormService pageFormService; private final ResourceService resourceService; @@ -35,7 +62,97 @@ public class IndicatorForm implements TemplateComposer { @Override public void compose(final PageContext pageContext) { - // TODO Auto-generated method stub + final CurrentUser currentUser = this.resourceService.getCurrentUser(); + final RestService restService = this.resourceService.getRestService(); + final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); + final I18nSupport i18nSupport = this.resourceService.getI18nSupport(); + + final UserInfo user = currentUser.get(); + 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() + .get(pageContext::notifyError); + + // get data or create new. Handle error if happen + final Indicator indicator = (isNew) + ? Indicator.createNew(exam.getInstitutionId(), exam) + : restService + .getBuilder(GetIndicator.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .get(pageContext::notifyError); + + if (indicator == null) { + log.error("Failed to get Indicator. " + + "Error was notified to the User. " + + "See previous logs for more infomation"); + return; + } + + // new PageContext with actual EntityKey + final PageContext formContext = pageContext.withEntityKey(indicator.getEntityKey()); + + // the default page layout + final LocTextKey titleKey = new LocTextKey( + (isNew) + ? "sebserver.exam.indicator.form.title.new" + : "sebserver.exam.indicator.form.title"); + final Composite content = widgetFactory.defaultPageLayout( + formContext.getParent(), + titleKey); + + final FormHandle formHandle = this.pageFormService.getBuilder( + formContext.copyOf(content), 4) + .readonly(isReadonly) + .putStaticValueIf(() -> !isNew, + Domain.INDICATOR.ATTR_ID, + indicator.getModelId()) + .putStaticValue( + Domain.EXAM.ATTR_INSTITUTION_ID, + exam.getModelId()) + .putStaticValue( + Domain.INDICATOR.ATTR_EXAM_ID, + parentEntityKey.getModelId()) + .addField(FormBuilder.text( + "examName", + "sebserver.exam.indicator.form.exam", + exam.name) + .readonly(true)) + .addField(FormBuilder.text( + Domain.INDICATOR.ATTR_NAME, + "sebserver.exam.indicator.form.name", + indicator.name)) + .addField(FormBuilder.singleSelection( + Domain.INDICATOR.ATTR_TYPE, + "sebserver.exam.indicator.form.type", + (indicator.type != null) ? indicator.type.name() : null, + this.resourceService::indicatorTypeResources)) + .addField(FormBuilder.colorSelection( + Domain.INDICATOR.ATTR_TYPE, + "sebserver.exam.indicator.form.color", + indicator.defaultColor)) + .buildFor((isNew) + ? restService.getRestCall(NewIndicator.class) + : restService.getRestCall(SaveIndicator.class)); + + // propagate content actions to action-pane + formContext.clearEntityKeys() + .createAction(ActionDefinition.EXAM_INDICATOR_SAVE) + .withParentEntityKey(parentEntityKey) + .withExec(formHandle::processFormSave) + .publishIf(() -> !isReadonly) + + .createAction(ActionDefinition.EXAM_INDICATOR_CANCEL_MODIFY) + .withEntityKey(parentEntityKey) + .withExec(Action::onEmptyEntityKeyGoToActivityHome) + .withConfirm("sebserver.overall.action.modify.cancel.confirm") + .publishIf(() -> !isReadonly); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java index fa5768c7..cb3423ee 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java @@ -72,7 +72,7 @@ public class InstitutionForm implements TemplateComposer { final EntityKey entityKey = pageContext.getEntityKey(); final boolean isNew = entityKey == null; // get data or create new. Handle error if happen - final Institution institution = (entityKey == null) + final Institution institution = (isNew) ? Institution.createNew() : this.restService .getBuilder(GetInstitution.class) @@ -116,7 +116,7 @@ public class InstitutionForm implements TemplateComposer { // The Institution form final FormHandle formHandle = this.pageFormService.getBuilder( formContext.copyOf(content), 4) - .readonly(formContext.isReadonly()) + .readonly(isReadonly) .putStaticValueIf(() -> !isNew, Domain.INSTITUTION.ATTR_ID, institution.getModelId()) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java index dd1ce4b6..96c66b45 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java @@ -196,7 +196,7 @@ public class LmsSetupForm implements TemplateComposer { .createAction(ActionDefinition.LMS_SETUP_ACTIVATE) .withEntityKey(entityKey) - .withExec(restService::activation) + .withExec(action -> activate(action, formHandle)) .publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive()) .createAction(ActionDefinition.LMS_SETUP_SAVE) @@ -210,6 +210,14 @@ public class LmsSetupForm implements TemplateComposer { .publishIf(() -> !readonly); } + /** Save and test connection before activation */ + private Action activate(final Action action, final FormHandle formHandle) { + final RestService restService = this.resourceService.getRestService(); + final Action testLmsSetup = this.testLmsSetup(action, formHandle); + final Action activation = restService.activation(testLmsSetup); + return activation; + } + /** LmsSetup test action implementation */ private Action testLmsSetup(final Action action, final FormHandle formHandle) { // If we are in edit-mode we have to save the form before testing diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 8a8efc27..0824f29d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -321,7 +321,6 @@ public enum ActionDefinition { ImageIcon.NEW, IndicatorForm.class, EXAM_VIEW_FROM_LIST, - ActionCategory.FORM, false), EXAM_INDICATOR_MODIFY_FROM_LIST( new LocTextKey("sebserver.exam.indicator.action.list.modify"), @@ -338,7 +337,7 @@ public enum ActionDefinition { ActionCategory.INDICATOR_LIST, true), EXAM_INDICATOR_SAVE( - new LocTextKey("sebserver.exam.indicator.action.list.save"), + new LocTextKey("sebserver.exam.indicator.action.save"), ImageIcon.SAVE, ExamForm.class, EXAM_VIEW_FROM_LIST, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java index cd021a3a..f6b9f469 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java @@ -195,7 +195,9 @@ public final class Form implements FormBinding { } private FormFieldAccessor createAccessor(final Label label, final Selection selection) { switch (selection.type()) { - case MULTI : return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray); + case MULTI: + case MULTI_COMBO: + return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray); default : return createAccessor(label, selection, null); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java index ce1bccbf..34bc940a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java @@ -216,6 +216,14 @@ public class FormBuilder { return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier); } + public static SelectionFieldBuilder colorSelection( + final String name, + final String label, + final String value) { + + return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null); + } + public static ImageUploadFieldBuilder imageUpload(final String name, final String label, final String value) { return new ImageUploadFieldBuilder(name, label, value); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java index 1f09f780..41898fcb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java @@ -89,6 +89,7 @@ public final class SelectionFieldBuilder extends FieldBuilder { if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO) { final Composite composite = new Composite(builder.formParent, SWT.NONE); final GridLayout gridLayout = new GridLayout(1, true); + gridLayout.verticalSpacing = 0; gridLayout.horizontalSpacing = 0; gridLayout.marginLeft = 0; gridLayout.marginHeight = 0; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java index cd292f2d..019e876d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java @@ -206,7 +206,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol try { final OAuth2AccessToken accessToken = this.restTemplate.getAccessToken(); - log.debug("Got token for user: {} : {}", username, accessToken); + log.debug("Got token for user: {} : {}", username, "--"); this.loggedInUser = getLoggedInUser(); return true; } catch (final OAuth2AccessDeniedException | AccessDeniedException e) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ColorSelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ColorSelection.java new file mode 100644 index 00000000..25625469 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ColorSelection.java @@ -0,0 +1,146 @@ +/* + * 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.List; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.ColorDialog; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.ethz.seb.sebserver.gbl.util.Tuple; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; + +public class ColorSelection extends Composite implements Selection { + + private static final long serialVersionUID = 4775375044147842526L; + private static final Logger log = LoggerFactory.getLogger(ColorSelection.class); + + private static final int ACTION_COLUMN_WIDTH = 20; + private final ColorDialog colorDialog; + private final Composite colorField; + private RGB selection; + + ColorSelection(final Composite parent, final WidgetFactory widgetFactory) { + super(parent, SWT.NONE); + final GridLayout gridLayout = new GridLayout(2, false); + gridLayout.verticalSpacing = 1; + gridLayout.marginLeft = 0; + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; + setLayout(gridLayout); + + this.colorDialog = new ColorDialog(this.getShell(), SWT.NONE); + + this.colorField = new Composite(this, SWT.NONE); + final GridData colorCell = new GridData(SWT.FILL, SWT.TOP, true, false); + colorCell.heightHint = 20; + this.colorField.setLayoutData(colorCell); + + final Label imageButton = widgetFactory.imageButton( + ImageIcon.COLOR, + this, + new LocTextKey("Set Color"), + this::addColorSelection); + + final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false); + actionCell.widthHint = ACTION_COLUMN_WIDTH; + imageButton.setLayoutData(actionCell); + + this.addListener(SWT.Resize, this::adaptColumnWidth); + } + + @Override + public Type type() { + return Type.COLOR; + } + + @Override + public void applyNewMapping(final List> mapping) { + } + + @Override + public void select(final String keys) { + this.selection = parseRGB(keys); + applySelection(); + } + + @Override + public String getSelectionValue() { + return parseColorString(this.selection); + } + + @Override + public void clear() { + this.selection = null; + } + + private void addColorSelection(final Event event) { + this.colorDialog.open(code -> { + if (code == SWT.CANCEL) { + return; + } + + this.selection = this.colorDialog.getRGB(); + applySelection(); + }); + } + + private void applySelection() { + if (this.selection != null) { + this.colorField.setBackground(new Color(this.getDisplay(), this.selection)); + } else { + this.colorField.setBackground(null); + } + } + + private void adaptColumnWidth(final Event event) { + try { + final int currentTableWidth = this.getClientArea().width; + final GridData comboCell = (GridData) this.colorField.getLayoutData(); + comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH; + this.layout(); + } catch (final Exception e) { + log.warn("Failed to adaptColumnWidth: ", e); + } + } + + static String parseColorString(final RGB color) { + if (color == null) { + return null; + } + + return Integer.toHexString(color.red) + + Integer.toHexString(color.green) + + Integer.toHexString(color.blue); + } + + static RGB parseRGB(final String colorString) { + if (StringUtils.isBlank(colorString)) { + return null; + } + + final int r = Integer.parseInt(colorString.substring(0, 2), 16); + final int g = Integer.parseInt(colorString.substring(2, 4), 16); + final int b = Integer.parseInt(colorString.substring(4, 6), 16); + + return new RGB(r, g, b); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java index de6f2fe1..f0cfe6f9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java @@ -26,6 +26,7 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; @@ -51,14 +52,14 @@ public class ImageUpload extends Composite { ImageUpload(final Composite parent, final ServerPushService serverPushService, final boolean readonly) { super(parent, SWT.NONE); - super.setLayout(new GridLayout(2, false)); + super.setLayout(new GridLayout(1, false)); this.serverPushService = serverPushService; if (!readonly) { this.fileUpload = new FileUpload(this, SWT.NONE); - this.fileUpload.setText("Select File"); - this.fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); + this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay())); + this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); final FileUploadHandler uploadHandler = new FileUploadHandler(new FileUploadReceiver() { @@ -108,7 +109,7 @@ public class ImageUpload extends Composite { public void setSelectionText(final String text) { if (this.fileUpload != null) { - this.fileUpload.setText(text); + this.fileUpload.setToolTipText(text); } } @@ -124,8 +125,8 @@ public class ImageUpload extends Composite { this.imageBase64 = imageBase64; final Base64InputStream input = new Base64InputStream( new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), false); - this.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage"); - this.imageCanvas.setBackgroundImage(new Image(super.getDisplay(), input)); + + setImage(this, input); } private static final boolean uploadInProgress(final ServerPushContext context) { @@ -153,12 +154,20 @@ public class ImageUpload extends Composite { imageUpload.imageBase64.getBytes(StandardCharsets.UTF_8)), false); - imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage"); - imageUpload.imageCanvas.setBackgroundImage(new Image(context.getDisplay(), input)); + setImage(imageUpload, input); context.layout(); imageUpload.layout(); imageUpload.loadNewImage = false; } } + private static void setImage(final ImageUpload imageUpload, final Base64InputStream input) { + imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage"); + + final Image image = new Image(imageUpload.imageCanvas.getDisplay(), input); + final ImageData imageData = image.getImageData().scaledTo(200, 100); + + imageUpload.imageCanvas.setBackgroundImage(new Image(imageUpload.imageCanvas.getDisplay(), imageData)); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java index 5bf96414..6ca8de53 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java @@ -9,18 +9,23 @@ package ch.ethz.seb.sebserver.gui.widget; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.TableEditor; +import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Label; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,43 +39,40 @@ public class MultiSelectionCombo extends Composite implements Selection { private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); private static final long serialVersionUID = -7787134114963647332L; private static final int ACTION_COLUMN_WIDTH = 20; + private static final String SELECTION_KEY = "SELECTION_KEY"; private final WidgetFactory widgetFactory; - private final Table table; private final Combo combo; - private final List selected = new ArrayList<>(); - private final List> mapping = new ArrayList<>(); + + private final List> selectionControls = new ArrayList<>(); + private final List> selectedValues = new ArrayList<>(); + private final Map mapping = new HashMap<>(); + //private final List> mapping = new ArrayList<>(); MultiSelectionCombo(final Composite parent, final WidgetFactory widgetFactory) { super(parent, SWT.NONE); this.widgetFactory = widgetFactory; - final GridLayout gridLayout = new GridLayout(1, true); + final GridLayout gridLayout = new GridLayout(2, false); gridLayout.verticalSpacing = 1; gridLayout.marginLeft = 0; gridLayout.marginHeight = 0; gridLayout.marginWidth = 0; setLayout(gridLayout); - this.table = new Table(this, SWT.NONE); + this.addListener(SWT.Resize, this::adaptColumnWidth); - TableColumn column = new TableColumn(this.table, SWT.NONE); - column = new TableColumn(this.table, SWT.NONE); - column.setWidth(ACTION_COLUMN_WIDTH); - this.table.setHeaderVisible(false); - this.table.addListener(SWT.Resize, this::adaptColumnWidth); + this.combo = new Combo(this, SWT.NONE); + final GridData comboCell = new GridData(SWT.FILL, SWT.CENTER, true, false); + this.combo.setLayoutData(comboCell); - final TableItem header = new TableItem(this.table, SWT.NONE); - final TableEditor editor = new TableEditor(this.table); - this.combo = new Combo(this.table, SWT.NONE); - editor.grabHorizontal = true; - editor.setEditor(this.combo, header, 0); - - widgetFactory.imageButton( - ImageIcon.ADD, - this.table, + final Label imageButton = widgetFactory.imageButton( + ImageIcon.ADD_BOX, + this, new LocTextKey("Add"), this::addComboSelection); - + final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false); + actionCell.widthHint = ACTION_COLUMN_WIDTH; + imageButton.setLayoutData(actionCell); } @Override @@ -80,49 +82,132 @@ public class MultiSelectionCombo extends Composite implements Selection { @Override public void applyNewMapping(final List> mapping) { - this.selected.clear(); - this.mapping.clear(); - this.mapping.addAll(mapping); - + this.mapping.putAll(mapping.stream() + .collect(Collectors.toMap(t -> t._1, t -> t._2))); + this.clear(); } @Override public void select(final String keys) { clear(); + if (StringUtils.isBlank(keys)) { + return; + } + Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR)) + .stream() + .forEach(this::addSelection); } @Override public String getSelectionValue() { - if (this.selected.isEmpty()) { + if (this.selectedValues.isEmpty()) { return null; } - return this.mapping + return this.selectedValues .stream() - .filter(t -> this.selected.contains(t._2)) .map(t -> t._1) - .reduce("", (s1, s2) -> s1.concat(Constants.LIST_SEPARATOR).concat(s2)); + .reduce("", (s1, s2) -> { + if (!StringUtils.isBlank(s1)) { + return s1.concat(Constants.LIST_SEPARATOR).concat(s2); + } else { + return s1.concat(s2); + } + }); } @Override public void clear() { - this.selected.clear(); - this.table.remove(1, this.table.getItemCount()); - final List names = this.mapping + this.selectedValues.clear(); + this.selectionControls .stream() - .map(t -> t._2) - .collect(Collectors.toList()); - this.combo.setItems(names.toArray(new String[names.size()])); + .forEach(t -> { + t._1.dispose(); + t._2.dispose(); + }); + this.selectionControls.clear(); + this.combo.setItems(this.mapping.values().toArray(new String[this.mapping.size()])); } private void addComboSelection(final Event event) { + final int selectionIndex = this.combo.getSelectionIndex(); + if (selectionIndex < 0) { + return; + } + final String itemName = this.combo.getItem(selectionIndex); + if (itemName == null) { + return; + } + + final Optional> findFirst = this.mapping.entrySet() + .stream() + .filter(entity -> entity.getValue().equals(itemName)) + .findFirst(); + + if (!findFirst.isPresent()) { + return; + } + + addSelection(findFirst.get().getKey()); + } + + private void addSelection(final String itemKey) { + final String itemName = this.mapping.get(itemKey); + if (itemName == null) { + return; + } + + this.selectedValues.add(new Tuple<>(itemKey, itemName)); + final Label label = this.widgetFactory.label(this, itemName); + final Label imageButton = this.widgetFactory.imageButton( + ImageIcon.REMOVE_BOX, + this, + new LocTextKey("Remove"), + this::removeComboSelection); + imageButton.setData(SELECTION_KEY, itemName); + final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false); + actionCell.widthHint = ACTION_COLUMN_WIDTH; + imageButton.setLayoutData(actionCell); + + this.selectionControls.add(new Tuple<>(label, imageButton)); + + this.combo.remove(itemName); + this.getParent().layout(); + } + + private void removeComboSelection(final Event event) { + if (event.widget == null) { + return; + } + + final String selectionKey = (String) event.widget.getData(SELECTION_KEY); + final Optional> findFirst = this.selectionControls.stream() + .filter(t -> selectionKey.equals(t._2.getData(SELECTION_KEY))) + .findFirst(); + if (!findFirst.isPresent()) { + return; + } + + final Tuple tuple = findFirst.get(); + final int indexOf = this.selectionControls.indexOf(tuple); + this.selectionControls.remove(tuple); + + tuple._1.dispose(); + tuple._2.dispose(); + + final Tuple value = this.selectedValues.remove(indexOf); + this.combo.add(value._2, this.combo.getItemCount()); + + this.getParent().layout(); } private void adaptColumnWidth(final Event event) { try { - final int currentTableWidth = this.table.getParent().getClientArea().width; - this.table.getColumn(0).setWidth(currentTableWidth - ACTION_COLUMN_WIDTH); + final int currentTableWidth = this.getClientArea().width; + final GridData comboCell = (GridData) this.combo.getLayoutData(); + comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH; + this.layout(); } catch (final Exception e) { log.warn("Failed to adaptColumnWidth: ", e); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java index 074a7e60..e2bb3b80 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java @@ -19,7 +19,8 @@ public interface Selection { enum Type { SINGLE, MULTI, - MULTI_COMBO + MULTI_COMBO, + COLOR, } Type type(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java new file mode 100644 index 00000000..d8b73e9b --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java @@ -0,0 +1,13 @@ +/* + * 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; + +public class ThresholdList { + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index 434a238a..d160a0de 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -62,6 +62,8 @@ public class WidgetFactory { MINIMIZE("minimize.png"), ADD("add.png"), REMOVE("remove.png"), + ADD_BOX("add_box.png"), + REMOVE_BOX("remove_box.png"), EDIT("edit.png"), TEST("test.png"), IMPORT("import.png"), @@ -73,7 +75,8 @@ public class WidgetFactory { SAVE("save.png"), NEW("new.png"), DELETE("delete.png"), - SEARCH("lens.png"); + SEARCH("lens.png"), + COLOR("color.png"); private String fileName; private ImageData image = null; @@ -325,49 +328,21 @@ public class WidgetFactory { case MULTI_COMBO: selection = new MultiSelectionCombo(parent, this); break; + case COLOR: + selection = new ColorSelection(parent, this); + break; default: throw new IllegalArgumentException("Unsupported Selection.Type: " + type); } - final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); - selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); - updateFunction.accept(selection); + if (itemsSupplier != null) { + final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); + selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); + updateFunction.accept(selection); + } return selection; } -// public SingleSelection singleSelectionLocalized( -// final Composite parent, -// final Supplier>> itemsSupplier) { -// -// final SingleSelection singleSelection = new SingleSelection(parent); -// final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); -// singleSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); -// updateFunction.accept(singleSelection); -// return singleSelection; -// } -// -// public MultiSelection multiSelectionLocalized( -// final Composite parent, -// final Supplier>> itemsSupplier) { -// -// final MultiSelection multiSelection = new MultiSelection(parent); -// final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); -// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); -// updateFunction.accept(multiSelection); -// return multiSelection; -// } -// -// public MultiSelectionCombo multiSelectionComboLocalized( -// final Composite parent, -// final Supplier>> itemsSupplier) { -// -// final MultiSelectionCombo multiSelection = new MultiSelectionCombo(parent, this); -// final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); -// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); -// updateFunction.accept(multiSelection); -// return multiSelection; -// } - public ImageUpload imageUploadLocalized( final Composite parent, final LocTextKey locTextKey, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java index ce7b6147..27462b25 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java @@ -107,7 +107,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { .withInstitutionalPrivilege(PrivilegeType.MODIFY) .withOwnerPrivilege(PrivilegeType.WRITE) .andForRole(UserRole.EXAM_SUPPORTER) - .withOwnerPrivilege(PrivilegeType.MODIFY) + .withInstitutionalPrivilege(PrivilegeType.READ_ONLY) .create(); // grants for indicators @@ -120,7 +120,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { .withInstitutionalPrivilege(PrivilegeType.MODIFY) .withOwnerPrivilege(PrivilegeType.WRITE) .andForRole(UserRole.EXAM_SUPPORTER) - .withOwnerPrivilege(PrivilegeType.MODIFY) + .withInstitutionalPrivilege(PrivilegeType.READ_ONLY) .create(); // TODO other entities diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java index 7f56026d..5f2d211f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java @@ -12,6 +12,8 @@ import java.io.UnsupportedEncodingException; import java.security.SecureRandom; import org.apache.commons.lang3.RandomStringUtils; +import org.cryptonode.jncryptor.AES256JNCryptor; +import org.cryptonode.jncryptor.JNCryptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; @@ -35,6 +37,9 @@ public class ClientCredentialServiceImpl implements ClientCredentialService { private final Environment environment; + // TODO try to integrate with JNCryptor since this is also used by SEB + private final JNCryptor cryptor = new AES256JNCryptor(); + protected ClientCredentialServiceImpl(final Environment environment) { this.environment = environment; } @@ -142,6 +147,7 @@ public class ClientCredentialServiceImpl implements ClientCredentialService { } try { + return Encryptors .delux(secret, getSalt(salt)) .encrypt(text.toString()); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java index 50c885de..785aa1fb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java @@ -95,7 +95,9 @@ public interface EntityDAO { * reported exception on error case */ Result save(T data); - /** Use this to delete a set Entity by a Collection of EntityKey + /** Use this to delete all entities defined by a set of EntityKey + * NOTE: the Set of EntityKey may contain EntityKey of other entity types like the concrete type of the DAO + * use extractPKsFromKeys to get a list of concrete primary keys for entities to delete * * @param all The Collection of EntityKey to delete * @return Result referring a collection of all entities that has been deleted or refer to an error if @@ -104,12 +106,12 @@ public interface EntityDAO { /** Get a (unordered) collection of all Entities that matches the given filter criteria. * The possible filter criteria for a specific Entity type is defined by the entity type. - * + * * This adds filtering in SQL level by creating the select where clause from related * filter criteria of the specific Entity type. If the filterMap contains a value for * a particular filter criteria the value is extracted from the map and added to the where * clause of the SQL select statement. - * + * * @param filterMap FilterMap instance containing all the relevant filter criteria * @return Result referring to collection of all matching entities or an error if happened */ @Transactional(readOnly = true) @@ -119,16 +121,16 @@ public interface EntityDAO { /** Get a (unordered) collection of all Entities that matches a given filter criteria * and a given predicate. - * + * * The possible filter criteria for a specific Entity type is defined by the entity type. * This adds filtering in SQL level by creating the select where clause from related * filter criteria of the specific Entity type. If the filterMap contains a value for * a particular filter criteria the value is extracted from the map and added to the where * clause of the SQL select statement. - * + * * The predicate is applied after the SQL query by filtering the resulting list with the * predicate after on the SQL query result, before returning. - * + * * @param filterMap FilterMap instance containing all the relevant filter criteria * @return Result referring to collection of all matching entities or an error if happened */ Result> allMatching(FilterMap filterMap, Predicate predicate); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java index 0ba770bb..c3d68512 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java @@ -261,8 +261,6 @@ public class IndicatorDAOImpl implements IndicatorDAO { .execute() .stream() .map(tRec -> new Threshold( - tRec.getId(), - tRec.getIndicatorId(), tRec.getValue().doubleValue(), tRec.getColor())) .collect(Collectors.toList()); @@ -270,8 +268,8 @@ public class IndicatorDAOImpl implements IndicatorDAO { return new Indicator( record.getId(), examRecord.getInstitutionId(), - examRecord.getOwner(), record.getExamId(), + examRecord.getOwner(), record.getName(), IndicatorType.valueOf(record.getType()), record.getColor(), diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java index 528bdcc8..f22dfa8b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java @@ -57,25 +57,25 @@ final class MockupLmsAPITemplate implements LmsAPITemplate { this.mockups = new ArrayList<>(); this.mockups.add(new QuizData( "quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1", "Demo Quit Mockup", - "2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); + "2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/")); this.mockups.add(new QuizData( "quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2", "Demo Quit Mockup", - "2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); + "2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/")); this.mockups.add(new QuizData( "quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3", "Demo Quit Mockup", - "2018-07-30 09:00:00", "2018-08-01 00:00:00", "http://lms.mockup.com/api/")); + "2018-07-30T09:00:00", "2018-08-01T00:00:00", "http://lms.mockup.com/api/")); this.mockups.add(new QuizData( "quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4", "Demo Quit Mockup", - "2018-01-01 00:00:00", "2019-01-01 00:00:00", "http://lms.mockup.com/api/")); + "2018-01-01T00:00:00", "2019-01-01T00:00:00", "http://lms.mockup.com/api/")); this.mockups.add(new QuizData( "quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5", "Demo Quit Mockup", - "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); + "2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/")); this.mockups.add(new QuizData( "quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6", "Demo Quit Mockup", - "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); + "2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/")); this.mockups.add(new QuizData( "quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7", "Demo Quit Mockup", - "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); + "2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/")); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java index dee9f595..3580d3fc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java @@ -25,9 +25,13 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RequestAuthenticator; import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.http.AccessTokenRequiredException; import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; @@ -115,6 +119,19 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { this.knownTokenAccessPaths); } + try { + this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT); + } catch (final Exception e) { + if (this.restTemplate != null) { + this.restTemplate.setAuthenticator(new EdxOAuth2RequestAuthenticator()); + } + try { + this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT); + } catch (final Exception ee) { + return LmsSetupTestResult.ofQuizRequestError(ee.getMessage()); + } + } + return LmsSetupTestResult.ofOkay(); } @@ -192,28 +209,6 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { return template; } -// private Result> getAllQuizes(final LmsSetup lmsSetup) { -// if (this.allQuizzesSupplier == null) { -// this.allQuizzesSupplier = new CircuitBreaker<>( -// () -> collectAllCourses(lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT) -// .stream() -// .reduce( -// new ArrayList(), -// (list, courseData) -> { -// list.add(quizDataOf(lmsSetup, courseData)); -// return list; -// }, -// (list1, list2) -> { -// list1.addAll(list2); -// return list1; -// }), -// 5, 1000L); // TODO specify better CircuitBreaker params -// } -// -// return this.allQuizzesSupplier.get(); -// -// } - private Supplier> allQuizzesSupplier(final LmsSetup lmsSetup) { return () -> { return initRestTemplateAndRequestAccessToken() @@ -320,8 +315,27 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { params.add("client_id", resource.getClientId()); params.add("client_secret", resource.getClientSecret()); - return retrieveToken(request, resource, params, headers); + final OAuth2AccessToken retrieveToken = retrieveToken(request, resource, params, headers); + return retrieveToken; } } + private class EdxOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator { + + @Override + public void authenticate( + final OAuth2ProtectedResourceDetails resource, + final OAuth2ClientContext clientContext, + final ClientHttpRequest request) { + + final OAuth2AccessToken accessToken = clientContext.getAccessToken(); + if (accessToken == null) { + throw new AccessTokenRequiredException(resource); + } + + request.getHeaders().set("Authorization", String.format("%s %s", "JWT:", accessToken.getValue())); + } + + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIController.java new file mode 100644 index 00000000..bf5d0cb0 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIController.java @@ -0,0 +1,86 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.MediaType; +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.model.seb.PingResponse; +import ch.ethz.seb.sebserver.gbl.model.seb.RunningExams; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; + +@WebServiceProfile +@RestController +@RequestMapping("/${sebserver.webservice.api.exam.endpoint}") +public class ExamAPIController { + + @RequestMapping( + path = API.EXAM_API_HANDSHAKE_ENDPOINT, + method = RequestMethod.GET, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public RunningExams handshake( + @RequestParam(name = API.PARAM_INSTITUTION_ID, required = true) final Long institutionId, + final HttpServletRequest request, + final HttpServletResponse response) { + + // TODO + return null; + } + + @RequestMapping( + path = API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT, + method = RequestMethod.GET, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.TEXT_XML_VALUE) + public RunningExams getConfig( + @RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, + @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = true) final String examId, + final HttpServletRequest request, + final HttpServletResponse response) { + + // TODO + return null; + } + + @RequestMapping( + path = API.EXAM_API_PING_ENDPOINT, + method = RequestMethod.PUT, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.TEXT_XML_VALUE) + public PingResponse ping( + @RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, + final HttpServletRequest request, + final HttpServletResponse response) { + + // TODO + return null; + } + + @RequestMapping( + path = API.EXAM_API_EVENT_ENDPOINT, + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public void event( + @RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, + final HttpServletRequest request, + final HttpServletResponse response) { + + // TODO + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamIndicatorController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/IndicatorController.java similarity index 88% rename from src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamIndicatorController.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/IndicatorController.java index e5151b2e..62535887 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamIndicatorController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/IndicatorController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; +import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.IndicatorRecordDynamicSqlSupport; @@ -28,11 +29,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe @WebServiceProfile @RestController @RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.EXAM_INDICATOR_ENDPOINT) -public class ExamIndicatorController extends EntityController { +public class IndicatorController extends EntityController { private final ExamDAO examDao; - protected ExamIndicatorController( + protected IndicatorController( final AuthorizationService authorization, final BulkActionService bulkActionService, final IndicatorDAO entityDAO, @@ -53,7 +54,7 @@ public class ExamIndicatorController extends EntityController checkPasswordChange(ui, passwordChange)) + .flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword())) + .flatMap(this::revokeAccessToken) + .flatMap(e -> this.userActivityLogDAO.log(ActivityType.PASSWORD_CHANGE, e)) + .getOrThrow(); + } + + private Result revokeAccessToken(final UserInfo userInfo) { + return Result.tryCatch(() -> { + this.applicationEventPublisher.publishEvent( + new RevokeTokenEndpoint.RevokeTokenEvent(userInfo, userInfo.username)); + return userInfo; + }); + } + /** Additional consistency checks that has to be checked before create and save actions */ private Result additionalConsistencyChecks(final T userInfo) { return Result.tryCatch(() -> { @@ -194,29 +219,4 @@ public class UserAccountController extends ActivatableEntityController checkPasswordChange(ui, passwordChange)) - .flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword())) - .flatMap(this::revokeAccessToken) - .flatMap(e -> this.userActivityLogDAO.log(ActivityType.PASSWORD_CHANGE, e)) - .getOrThrow(); - } - - private Result revokeAccessToken(final UserInfo userInfo) { - return Result.tryCatch(() -> { - this.applicationEventPublisher.publishEvent( - new RevokeTokenEndpoint.RevokeTokenEvent(userInfo, userInfo.username)); - return userInfo; - }); - } - } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index c5006929..99ef0581 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -77,9 +77,9 @@ sebserver.institution.list.column.active=Active sebserver.institution.action.list=Institution sebserver.institution.action.form=Institution sebserver.institution.action.new=New Institution -sebserver.institution.action.list.view=View Selected -sebserver.institution.action.modify=Edit Institution -sebserver.institution.action.list.modify=Edit Selected +sebserver.institution.action.list.view=View Institution +sebserver.institution.action.list.modify=Edit Institution +sebserver.institution.action.modify=Edit sebserver.institution.action.save=Save Institution sebserver.institution.action.activate=Activate sebserver.institution.action.deactivate=Deactivate @@ -117,8 +117,8 @@ sebserver.useraccount.list.column.active=Active sebserver.useraccount.action.list=User Account sebserver.useraccount.action.form=User Account of {0} sebserver.useraccount.action.new=New User Account -sebserver.useraccount.action.view=View Selected -sebserver.useraccount.action.list.modify=Edit Selected +sebserver.useraccount.action.view=View User Account +sebserver.useraccount.action.list.modify=Edit User Account sebserver.useraccount.action.modify=Edit sebserver.useraccount.action.save=Save User Account sebserver.useraccount.action.activate=Activate @@ -165,8 +165,8 @@ sebserver.lmssetup.list.column.active=Active sebserver.lmssetup.action.list=LMS Setup sebserver.lmssetup.action.form=LMS Setup sebserver.lmssetup.action.new=New LMS Setup -sebserver.lmssetup.action.list.view=View Selected -sebserver.lmssetup.action.list.modify=Edit Selected +sebserver.lmssetup.action.list.view=View LMS Setup +sebserver.lmssetup.action.list.modify=Edit LMS Setup sebserver.lmssetup.action.modify=Edit sebserver.lmssetup.action.test=Test Setup sebserver.lmssetup.action.test.ok=Successfully connect to the LMSs course API @@ -225,6 +225,9 @@ sebserver.exam.list.column.type=Type sebserver.exam.list.empty=No Exams has been found. Please adapt the filter or import one from Quiz sebserver.exam.action.list=Exam +sebserver.exam.action.list.view=View Exam +sebserver.exam.action.list.modify=Edit Exam +sebserver.exam.action.modify=Edit sebserver.exam.action.import=Import From Quizzes sebserver.exam.action.save=Save sebserver.exam.action.activate=Activate @@ -242,6 +245,7 @@ sebserver.exam.form.starttime=Start Time sebserver.exam.form.endtime=End Time sebserver.exam.form.status=Status sebserver.exam.form.type=Exam Type +sebserver.exam.form.supporter=Exam Supporter sebserver.exam.type.UNDEFINED=Not Defined sebserver.exam.type.MANAGED=Managed @@ -252,8 +256,9 @@ sebserver.exam.indicator.list.actions=Selected Indicator sebserver.exam.indicator.list.title=Indicators sebserver.exam.indicator.list.column.type=Type sebserver.exam.indicator.list.column.name=Name -sebserver.exam.indicator.list.column.thresholds +sebserver.exam.indicator.list.column.thresholds=Thresholds 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 an Indicator first sebserver.exam.indicator.type.LAST_PING=Last Ping sebserver.exam.indicator.type.ERROR_COUNT=Error Count @@ -262,4 +267,13 @@ sebserver.exam.indicator.info.pleaseSelect=Please Select an Indicator first sebserver.exam.indicator.action.list.new=New Indicator sebserver.exam.indicator.action.list.modify=Modify Indicator +sebserver.exam.indicator.action.save=Save +sebserver.exam.indicator.form.title=Indicator +sebserver.exam.indicator.form.title.new=New Indicator +sebserver.exam.indicator.form.exam=Exam +sebserver.exam.indicator.form.name=Name +sebserver.exam.indicator.form.type=Type +sebserver.exam.indicator.form.color=Color + +sebserver.exam.indicator.thresholds.list.title=Thresholds diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index f02b34b8..3f0be26c 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -345,9 +345,7 @@ Button { /* Push Buttons */ Button[PUSH], -Button[PUSH]:default, -FileUpload, -FileUpload:default { +Button[PUSH]:default { font: bold 12px Arial, Helvetica, sans-serif; background-color: #0069B4; background-gradient-color: #0069B4; @@ -359,16 +357,14 @@ FileUpload:default { text-shadow: none; } -Button[PUSH]:pressed, -FileUpload:pressed { +Button[PUSH]:pressed { background-color: #444; color: #fff; background-gradient-color: #444; background-image: gradient( linear, left top, left bottom, from( #444 ), to( #444 ) ); } -Button[PUSH]:hover, -FileUpload:hover { +Button[PUSH]:hover { background-color: #82BE1E; background-gradient-color: #82BE1E; background-image: gradient( linear, left top, left bottom, from( #82BE1E ), to( #82BE1E ) ); @@ -376,8 +372,7 @@ FileUpload:hover { cursor: pointer; } -Button[PUSH]:disabled, -FileUpload:disabled { +Button[PUSH]:disabled { background-color: transparent; border: 1px solid #EAECEE; color: #c0c0c0; @@ -418,6 +413,18 @@ Button[PUSH]:hover.header { cursor: pointer; } +FileUpload, +FileUpload:default, +FileUpload:hover, +FileUpload:pressed { + background-color: transparent; + background-gradient-color: transparent; + background-image: gradient( linear, left top, left bottom, from( transparent ), to( transparent ) ); + border: none; + border-radius: 0px; + text-shadow: none; +} + /* Sash default */ Sash { diff --git a/src/main/resources/static/images/add_box.png b/src/main/resources/static/images/add_box.png new file mode 100644 index 00000000..86527618 Binary files /dev/null and b/src/main/resources/static/images/add_box.png differ diff --git a/src/main/resources/static/images/color.png b/src/main/resources/static/images/color.png new file mode 100644 index 00000000..80e72e67 Binary files /dev/null and b/src/main/resources/static/images/color.png differ diff --git a/src/main/resources/static/images/remove_box.png b/src/main/resources/static/images/remove_box.png new file mode 100644 index 00000000..1c42a35b Binary files /dev/null and b/src/main/resources/static/images/remove_box.png differ diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/widget/ColorSelectionTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/widget/ColorSelectionTest.java new file mode 100644 index 00000000..495eb9da --- /dev/null +++ b/src/test/java/ch/ethz/seb/sebserver/gui/widget/ColorSelectionTest.java @@ -0,0 +1,38 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import org.eclipse.swt.graphics.RGB; +import org.junit.Test; + +public class ColorSelectionTest { + + @Test + public void testParseRGB() { + String colorString = "FFFFFF"; + assertEquals( + "RGB {255, 255, 255}", + ColorSelection.parseRGB(colorString).toString()); + + colorString = "FFaa34"; + assertEquals( + "RGB {255, 170, 52}", + ColorSelection.parseRGB(colorString).toString()); + } + + @Test + public void testParseColorString() { + final RGB color = new RGB(255, 255, 255); + assertEquals("ffffff", ColorSelection.parseColorString(color)); + + } + +}