SEBSERV-26 SEB client event logs

This commit is contained in:
anhefti 2019-08-07 12:57:25 +02:00
parent ccced32e1d
commit 47a0f37d9d
71 changed files with 1565 additions and 402 deletions

View file

@ -131,6 +131,11 @@ public final class API {
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection";
public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event"; public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event";
public static final String SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT = "/search";
public static final String SEB_CLIENT_EVENT_EXTENDED_PAGE_ENDPOINT = SEB_CLIENT_EVENT_ENDPOINT
+ SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT;
} }

View file

@ -50,9 +50,19 @@ public interface Entity extends ModelIdAware {
* @return EntityName instance created form given Entity */ * @return EntityName instance created form given Entity */
default EntityName toName() { default EntityName toName() {
return new EntityName( return new EntityName(
this.entityType(),
this.getModelId(), this.getModelId(),
this.entityType(),
this.getName()); this.getName());
} }
/** This can be overwritten if an entity contains security sensitive data
* Returns a representation of the entity that has no security sensitive data
* and an be print out to user logs or error messages
*
* @return representation of the entity that has no security sensitive data */
default Entity printSecureCopy() {
return this;
}
} }

View file

@ -13,12 +13,14 @@ import java.io.Serializable;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
/** A EntityKey uniquely identifies a domain entity within the SEB Server's domain model. /** A EntityKey uniquely identifies a domain entity within the SEB Server's domain model.
* A EntityKey consists of the model identifier of a domain entity and the type of the entity. */ * A EntityKey consists of the model identifier of a domain entity and the type of the entity. */
@JsonIgnoreProperties(ignoreUnknown = true)
public class EntityKey implements ModelIdAware, Serializable { public class EntityKey implements ModelIdAware, Serializable {
private static final long serialVersionUID = -2368065921846821061L; private static final long serialVersionUID = -2368065921846821061L;

View file

@ -17,33 +17,30 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class EntityName implements ModelIdAware { public class EntityName extends EntityKey {
private static final long serialVersionUID = 9577137222563155L;
@JsonProperty(value = API.PARAM_ENTITY_TYPE, required = true)
public final EntityType entityType;
@JsonProperty(value = API.PARAM_MODEL_ID, required = true)
public final String modelId;
@JsonProperty(value = "name", required = true) @JsonProperty(value = "name", required = true)
public final String name; public final String name;
@JsonCreator @JsonCreator
public EntityName( public EntityName(
@JsonProperty(value = API.PARAM_ENTITY_TYPE, required = true) final EntityType entityType,
@JsonProperty(value = API.PARAM_MODEL_ID, required = true) final String id, @JsonProperty(value = API.PARAM_MODEL_ID, required = true) final String id,
@JsonProperty(value = API.PARAM_ENTITY_TYPE, required = true) final EntityType entityType,
@JsonProperty(value = "name", required = true) final String name) { @JsonProperty(value = "name", required = true) final String name) {
this.entityType = entityType; super(id, entityType);
this.modelId = id;
this.name = name; this.name = name;
} }
public EntityName(final EntityKey entityKey, final String name) { public EntityName(final EntityKey entityKey, final String name) {
this.entityType = entityKey.entityType; super(entityKey.modelId, entityKey.entityType);
this.modelId = entityKey.modelId;
this.name = name; this.name = name;
} }
@Override
public EntityType getEntityType() { public EntityType getEntityType() {
return this.entityType; return this.entityType;
} }
@ -63,40 +60,6 @@ public class EntityName implements ModelIdAware {
return new EntityKey(getModelId(), getEntityType()); return new EntityKey(getModelId(), getEntityType());
} }
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.entityType == null) ? 0 : this.entityType.hashCode());
result = prime * result + ((this.modelId == null) ? 0 : this.modelId.hashCode());
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final EntityName other = (EntityName) obj;
if (this.entityType != other.entityType)
return false;
if (this.modelId == null) {
if (other.modelId != null)
return false;
} else if (!this.modelId.equals(other.modelId))
return false;
if (this.name == null) {
if (other.name != null)
return false;
} else if (!this.name.equals(other.name))
return false;
return true;
}
@Override @Override
public String toString() { public String toString() {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();

View file

@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; 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.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Activatable;
@ -30,6 +31,23 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public final class Exam implements GrantEntity, Activatable { public final class Exam implements GrantEntity, Activatable {
public static final Exam EMPTY_EXAM = new Exam(
-1L,
-1L,
-1L,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
null,
null,
Constants.EMPTY_NOTE,
ExamType.UNDEFINED,
null,
null,
null,
null,
false);
// TODO make this a configurable exam attribute // TODO make this a configurable exam attribute
/** The number of hours to add at the start- and end-time of the exam /** The number of hours to add at the start- and end-time of the exam
* To add a expanded time-frame in which the exam state is running on SEB-Server side */ * To add a expanded time-frame in which the exam state is running on SEB-Server side */

View file

@ -15,11 +15,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; 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.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_NODE; import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_NODE;
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM_CONFIGURATION_MAP; import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM_CONFIGURATION_MAP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
@ -143,10 +145,12 @@ public final class ExamConfigurationMap implements GrantEntity {
return this.userNames; return this.userNames;
} }
@JsonIgnore
public CharSequence getEncryptSecret() { public CharSequence getEncryptSecret() {
return this.encryptSecret; return this.encryptSecret;
} }
@JsonIgnore
public CharSequence getConfirmEncryptSecret() { public CharSequence getConfirmEncryptSecret() {
return this.confirmEncryptSecret; return this.confirmEncryptSecret;
} }
@ -168,6 +172,21 @@ public final class ExamConfigurationMap implements GrantEntity {
return this.configStatus; return this.configStatus;
} }
@Override
public Entity printSecureCopy() {
return new ExamConfigurationMap(
this.id,
this.institutionId,
this.examId,
this.configurationNodeId,
this.userNames,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
this.configName,
this.configDescription,
this.configStatus);
}
@Override @Override
public String toString() { public String toString() {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();

View file

@ -18,11 +18,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; 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.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.Domain.INSTITUTION; import ch.ethz.seb.sebserver.gbl.model.Domain.INSTITUTION;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP; import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityName; import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
@ -161,6 +163,20 @@ public final class LmsSetup implements GrantEntity, Activatable {
return this.active; return this.active;
} }
@Override
public Entity printSecureCopy() {
return new LmsSetup(
this.id,
this.institutionId,
this.name,
this.lmsType,
this.lmsAuthName,
Constants.EMPTY_NOTE,
this.lmsApiUrl,
Constants.EMPTY_NOTE,
this.active);
}
@Override @Override
public String toString() { public String toString() {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
@ -174,8 +190,6 @@ public final class LmsSetup implements GrantEntity, Activatable {
builder.append(this.lmsType); builder.append(this.lmsType);
builder.append(", lmsAuthName="); builder.append(", lmsAuthName=");
builder.append(this.lmsAuthName); builder.append(this.lmsAuthName);
builder.append(", lmsAuthSecret=");
builder.append(this.lmsAuthSecret);
builder.append(", lmsApiUrl="); builder.append(", lmsApiUrl=");
builder.append(this.lmsApiUrl); builder.append(this.lmsApiUrl);
builder.append(", lmsRestApiToken="); builder.append(", lmsRestApiToken=");
@ -188,8 +202,8 @@ public final class LmsSetup implements GrantEntity, Activatable {
public static EntityName toName(final LmsSetup lmsSetup) { public static EntityName toName(final LmsSetup lmsSetup) {
return new EntityName( return new EntityName(
EntityType.LMS_SETUP,
String.valueOf(lmsSetup.id), String.valueOf(lmsSetup.id),
EntityType.LMS_SETUP,
lmsSetup.name); lmsSetup.name);
} }

View file

@ -15,14 +15,17 @@ import org.hibernate.validator.constraints.URL;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; 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.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION; import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
public final class SebClientConfig implements GrantEntity, Activatable { public final class SebClientConfig implements GrantEntity, Activatable {
@ -60,6 +63,7 @@ public final class SebClientConfig implements GrantEntity, Activatable {
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE)
public final Boolean active; public final Boolean active;
@JsonCreator
public SebClientConfig( public SebClientConfig(
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId,
@ -130,10 +134,12 @@ public final class SebClientConfig implements GrantEntity, Activatable {
return this.date; return this.date;
} }
@JsonIgnore
public CharSequence getEncryptSecret() { public CharSequence getEncryptSecret() {
return this.encryptSecret; return this.encryptSecret;
} }
@JsonIgnore
public CharSequence getConfirmEncryptSecret() { public CharSequence getConfirmEncryptSecret() {
return this.confirmEncryptSecret; return this.confirmEncryptSecret;
} }
@ -147,6 +153,19 @@ public final class SebClientConfig implements GrantEntity, Activatable {
return this.active; return this.active;
} }
@Override
public Entity printSecureCopy() {
return new SebClientConfig(
this.id,
this.institutionId,
this.name,
this.fallbackStartURL,
this.date,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
this.active);
}
@Override @Override
public String toString() { public String toString() {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
@ -160,10 +179,6 @@ public final class SebClientConfig implements GrantEntity, Activatable {
builder.append(this.fallbackStartURL); builder.append(this.fallbackStartURL);
builder.append(", date="); builder.append(", date=");
builder.append(this.date); builder.append(this.date);
builder.append(", encryptSecret=");
builder.append(this.encryptSecret);
builder.append(", confirmEncryptSecret=");
builder.append(this.confirmEncryptSecret);
builder.append(", active="); builder.append(", active=");
builder.append(this.active); builder.append(this.active);
builder.append("]"); builder.append("]");

View file

@ -19,9 +19,6 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public final class ClientConnection implements GrantEntity { public final class ClientConnection implements GrantEntity {
public static final String FILTER_ATTR_EXAM_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_ID;
public static final String FILTER_ATTR_STATUS = Domain.CLIENT_CONNECTION.ATTR_STATUS;
public enum ConnectionStatus { public enum ConnectionStatus {
UNDEFINED, UNDEFINED,
CONNECTION_REQUESTED, CONNECTION_REQUESTED,
@ -32,6 +29,20 @@ public final class ClientConnection implements GrantEntity {
RELEASED RELEASED
} }
public static final ClientConnection EMPTY_CLIENT_CONNECTION = new ClientConnection(
-1L,
-1L,
-1L,
ConnectionStatus.UNDEFINED,
null,
null,
null,
null);
public static final String FILTER_ATTR_EXAM_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_ID;
public static final String FILTER_ATTR_STATUS = Domain.CLIENT_CONNECTION.ATTR_STATUS;
public static final String FILTER_ATTR_SESSION_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_IDENTIFER;
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
public final Long id; public final Long id;

View file

@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public final class ClientEvent implements Entity, IndicatorValueHolder { public class ClientEvent implements Entity, IndicatorValueHolder {
/** Adapt SEB API to SEB_SEB_Server API -> timestamp == clientTime */ /** Adapt SEB API to SEB_SEB_Server API -> timestamp == clientTime */
public static final String ATTR_TIMESTAMP = "timestamp"; public static final String ATTR_TIMESTAMP = "timestamp";

View file

@ -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.gbl.model.session;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.model.Domain;
public final class ExtendedClientEvent extends ClientEvent {
public static final String FILTER_ATTRIBUTE_EXAM = Domain.CLIENT_CONNECTION.ATTR_EXAM_ID;
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_INSTITUTION_ID)
public final Long institutionId;
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID)
public final Long examId;
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_IDENTIFER)
public final String userSessionId;
@JsonCreator
public ExtendedClientEvent(
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_INSTITUTION_ID) final Long institutionId,
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId,
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_IDENTIFER) final String userSessionId,
@JsonProperty(Domain.CLIENT_EVENT.ATTR_ID) final Long id,
@JsonProperty(Domain.CLIENT_EVENT.ATTR_CONNECTION_ID) final Long connectionId,
@JsonProperty(Domain.CLIENT_EVENT.ATTR_TYPE) final EventType eventType,
@JsonProperty(ATTR_TIMESTAMP) final Long clientTime,
@JsonProperty(Domain.CLIENT_EVENT.ATTR_SERVER_TIME) final Long serverTime,
@JsonProperty(Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE) final Double numValue,
@JsonProperty(Domain.CLIENT_EVENT.ATTR_TEXT) final String text) {
super(id, connectionId, eventType, clientTime, serverTime, numValue, text);
this.institutionId = institutionId;
this.examId = examId;
this.userSessionId = userSessionId;
}
public Long getInstitutionId() {
return this.institutionId;
}
public Long getExamId() {
return this.examId;
}
public String getUserSessionId() {
return this.userSessionId;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("ExtendedClientEvent [institutionId=");
builder.append(this.institutionId);
builder.append(", examId=");
builder.append(this.examId);
builder.append(", userSessionId=");
builder.append(this.userSessionId);
builder.append(", connectionId=");
builder.append(this.connectionId);
builder.append(", eventType=");
builder.append(this.eventType);
builder.append(", clientTime=");
builder.append(this.clientTime);
builder.append(", serverTime=");
builder.append(this.serverTime);
builder.append(", numValue=");
builder.append(this.numValue);
builder.append(", text=");
builder.append(this.text);
builder.append("]");
return builder.toString();
}
}

View file

@ -14,6 +14,7 @@ import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; 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.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER; import ch.ethz.seb.sebserver.gbl.model.Domain.USER;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
@ -85,4 +86,13 @@ public class PasswordChange implements Entity {
return "PasswordChange"; return "PasswordChange";
} }
@Override
public Entity printSecureCopy() {
return new PasswordChange(
this.userId,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE,
Constants.EMPTY_NOTE);
}
} }

View file

@ -405,5 +405,4 @@ public final class Utils {
org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE); org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE);
return headers; return headers;
} }
} }

View file

@ -303,7 +303,7 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_CONFIGURATION_MODIFY_FROM_LIST) .newAction(ActionDefinition.EXAM_CONFIGURATION_MODIFY_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
final EntityKey configMapKey = (configurationTable.hasAnyContent()) final EntityKey configMapKey = (configurationTable.hasAnyContent())
? configurationTable.getFirstRowData().getEntityKey() ? configurationTable.getFirstRowData().getEntityKey()
@ -369,7 +369,7 @@ public class ExamForm implements TemplateComposer {
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
actionBuilder actionBuilder

View file

@ -144,13 +144,13 @@ public class ExamList implements TemplateComposer {
.withColumn(new ColumnDefinition<>( .withColumn(new ColumnDefinition<>(
Domain.EXAM.ATTR_TYPE, Domain.EXAM.ATTR_TYPE,
COLUMN_TITLE_TYPE_KEY, COLUMN_TITLE_TYPE_KEY,
this.resourceService::examTypeName) this.resourceService::localizedExamTypeName)
.withFilter(this.typeFilter) .withFilter(this.typeFilter)
.sortable()) .sortable())
.withDefaultAction(actionBuilder .withDefaultAction(actionBuilder
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST) .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
// propagate content actions to action-pane // propagate content actions to action-pane
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM); final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);

View file

@ -100,7 +100,7 @@ public class InstitutionList implements TemplateComposer {
.withDefaultAction(pageActionBuilder .withDefaultAction(pageActionBuilder
.newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST) .newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
// propagate content actions to action-pane // propagate content actions to action-pane
final GrantCheck instGrant = this.currentUser.grantCheck(EntityType.INSTITUTION); final GrantCheck instGrant = this.currentUser.grantCheck(EntityType.INSTITUTION);

View file

@ -147,7 +147,7 @@ public class LmsSetupList implements TemplateComposer {
.withDefaultAction(actionBuilder .withDefaultAction(actionBuilder
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST) .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
// propagate content actions to action-pane // propagate content actions to action-pane
final GrantCheck userGrant = currentUser.grantCheck(EntityType.LMS_SETUP); final GrantCheck userGrant = currentUser.grantCheck(EntityType.LMS_SETUP);

View file

@ -169,10 +169,10 @@ public class MonitoringClientConnection implements TemplateComposer {
.withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam( .withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam(
ClientEvent.FILTER_ATTR_CONECTION_ID, ClientEvent.FILTER_ATTR_CONECTION_ID,
entityKey.modelId)) entityKey.modelId))
.withColumn(new ColumnDefinition<>( .withColumn(new ColumnDefinition<ClientEvent>(
Domain.CLIENT_EVENT.ATTR_TYPE, Domain.CLIENT_EVENT.ATTR_TYPE,
LIST_COLUMN_TYPE_KEY, LIST_COLUMN_TYPE_KEY,
this::getEventTypeName) this.resourceService::getEventTypeName)
.withFilter(this.typeFilter) .withFilter(this.typeFilter)
.sortable() .sortable()
.widthProportion(2)) .widthProportion(2))
@ -202,7 +202,7 @@ public class MonitoringClientConnection implements TemplateComposer {
.sortable() .sortable()
.widthProportion(1)) .widthProportion(1))
.compose(content); .compose(pageContext.copyOf(content));
this.pageService this.pageService
.pageActionBuilder( .pageActionBuilder(
@ -216,10 +216,6 @@ public class MonitoringClientConnection implements TemplateComposer {
} }
public String getEventTypeName(final ClientEvent clientEvent) {
return this.resourceService.getEventTypeName(clientEvent.eventType);
}
private final String getClientTime(final ClientEvent event) { private final String getClientTime(final ClientEvent event) {
if (event == null || event.getClientTime() == null) { if (event == null || event.getClientTime() == null) {
return Constants.EMPTY_NOTE; return Constants.EMPTY_NOTE;

View file

@ -151,10 +151,6 @@ public class MonitoringRunningExam implements TemplateComposer {
return pageAction; return pageAction;
}) })
// .withSelect(
// () -> clientTable.getSelection(),
// PageAction::applySingleSelection,
// EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER)); .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER));
} }

View file

@ -104,7 +104,7 @@ public class MonitoringRunningExamList implements TemplateComposer {
.withColumn(new ColumnDefinition<>( .withColumn(new ColumnDefinition<>(
Domain.EXAM.ATTR_TYPE, Domain.EXAM.ATTR_TYPE,
COLUMN_TITLE_TYPE_KEY, COLUMN_TITLE_TYPE_KEY,
this.resourceService::examTypeName) this.resourceService::localizedExamTypeName)
.withFilter(this.typeFilter) .withFilter(this.typeFilter)
.sortable()) .sortable())
.withColumn(new ColumnDefinition<>( .withColumn(new ColumnDefinition<>(
@ -124,7 +124,7 @@ public class MonitoringRunningExamList implements TemplateComposer {
.withDefaultAction(actionBuilder .withDefaultAction(actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST) .newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
actionBuilder actionBuilder

View file

@ -157,7 +157,7 @@ public class QuizDiscoveryList implements TemplateComposer {
.withExec(action -> this.showDetails(action, t.getSelectedROWData())) .withExec(action -> this.showDetails(action, t.getSelectedROWData()))
.noEventPropagation() .noEventPropagation()
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
// propagate content actions to action-pane // propagate content actions to action-pane
final GrantCheck lmsSetupGrant = currentUser.grantCheck(EntityType.LMS_SETUP); final GrantCheck lmsSetupGrant = currentUser.grantCheck(EntityType.LMS_SETUP);
@ -221,7 +221,11 @@ public class QuizDiscoveryList implements TemplateComposer {
} }
private void createDetailsForm(final QuizData quizData, final PageContext pc) { private void createDetailsForm(final QuizData quizData, final PageContext pc) {
this.pageService.formBuilder(pc, 3)
final Composite parent = pc.getParent();
final Composite grid = this.widgetFactory.createPopupScrollComposite(parent);
this.pageService.formBuilder(pc.copyOf(grid), 3)
.withEmptyCellSeparation(false) .withEmptyCellSeparation(false)
.readonly(true) .readonly(true)
.addField(FormBuilder.singleSelection( .addField(FormBuilder.singleSelection(
@ -251,7 +255,6 @@ public class QuizDiscoveryList implements TemplateComposer {
QUIZ_DETAILS_URL_TEXT_KEY, QUIZ_DETAILS_URL_TEXT_KEY,
quizData.startURL)) quizData.startURL))
.build(); .build();
this.widgetFactory.labelSeparator(pc.getParent());
} }
} }

View file

@ -151,7 +151,7 @@ public class SebClientConfigList implements TemplateComposer {
.withDefaultAction(pageActionBuilder .withDefaultAction(pageActionBuilder
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST) .newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
final GrantCheck clientConfigGrant = this.currentUser.grantCheck(EntityType.SEB_CLIENT_CONFIGURATION); final GrantCheck clientConfigGrant = this.currentUser.grantCheck(EntityType.SEB_CLIENT_CONFIGURATION);

View file

@ -8,26 +8,374 @@
package ch.ethz.seb.sebserver.gui.content; 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.context.annotation.Lazy;
import org.springframework.stereotype.Component; 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.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;
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.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 @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class SebClientLogs implements TemplateComposer { public class SebClientLogs implements TemplateComposer {
public SebClientLogs() { private static final Logger log = LoggerFactory.getLogger(SebClientLogs.class);
// TODO Auto-generated constructor stub
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::getExamResources);
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 @Override
public void compose(final PageContext pageContext) { public void compose(final PageContext pageContext) {
// TODO Auto-generated method stub final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final RestService restService = this.resourceService.getRestService();
// 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(
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_IDENTIFER,
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,
TIME_TEXT_KEY,
this::getEventTime)
.withFilter(new TableFilterAttribute(
CriteriaType.DATE_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.getSelectedROWData()))
.noEventPropagation()
.create())
.compose(pageContext.copyOf(content));
actionBuilder
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
.withSelect(
table::getSelection,
action -> this.showDetails(action, table.getSelectedROWData()),
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), 3)
.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)))
.addField(FormBuilder.text(
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
FORM_SERVERTIME_TEXT_KEY,
this.i18nSupport.formatDisplayDateTime(clientEvent.serverTime)))
.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 Exam for id {}", clientEvent.examId, error),
() -> ClientConnection.EMPTY_CLIENT_CONNECTION);
this.pageService.formBuilder(pc.copyOf(content), 3)
.withEmptyCellSeparation(false)
.readonly(true)
.addField(FormBuilder.text(
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_IDENTIFER,
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), 3)
.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.formatDisplayDateTime(exam.startTime)))
.addField(FormBuilder.text(
QuizData.QUIZ_ATTR_END_TIME,
FORM_END_TIME_TEXT_KEY,
this.i18nSupport.formatDisplayDateTime(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));
}
} }

View file

@ -137,7 +137,7 @@ public class SebExamConfigList implements TemplateComposer {
.withDefaultAction(pageActionBuilder .withDefaultAction(pageActionBuilder
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST) .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE); final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE);
pageActionBuilder pageActionBuilder

View file

@ -179,7 +179,7 @@ public class UserAccountList implements TemplateComposer {
.withDefaultAction(actionBuilder .withDefaultAction(actionBuilder
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST) .newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST)
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
// propagate content actions to action-pane // propagate content actions to action-pane
final GrantCheck userGrant = currentUser.grantCheck(EntityType.USER); final GrantCheck userGrant = currentUser.grantCheck(EntityType.USER);

View file

@ -30,7 +30,7 @@ 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.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.userlogs.GetUserLogPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetUserLogPage;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.EntityTable; import ch.ethz.seb.sebserver.gui.table.EntityTable;
@ -54,8 +54,10 @@ public class UserActivityLogs implements TemplateComposer {
new LocTextKey("sebserver.userlogs.list.column.dateTime"); new LocTextKey("sebserver.userlogs.list.column.dateTime");
private static final LocTextKey ACTIVITY_TEXT_KEY = private static final LocTextKey ACTIVITY_TEXT_KEY =
new LocTextKey("sebserver.userlogs.list.column.activityType"); new LocTextKey("sebserver.userlogs.list.column.activityType");
private static final LocTextKey ENTITY_TEXT_KEY = private static final LocTextKey ENTITY_TYPE_TEXT_KEY =
new LocTextKey("sebserver.userlogs.list.column.entityType"); new LocTextKey("sebserver.userlogs.list.column.entityType");
private static final LocTextKey ENTITY_ID_TEXT_KEY =
new LocTextKey("sebserver.userlogs.list.column.entityId");
private static final LocTextKey MESSAGE_TEXT_KEY = private static final LocTextKey MESSAGE_TEXT_KEY =
new LocTextKey("sebserver.userlogs.list.column.message"); new LocTextKey("sebserver.userlogs.list.column.message");
private final static LocTextKey EMPTY_SELECTION_TEXT = private final static LocTextKey EMPTY_SELECTION_TEXT =
@ -73,12 +75,11 @@ public class UserActivityLogs implements TemplateComposer {
public UserActivityLogs( public UserActivityLogs(
final PageService pageService, final PageService pageService,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService; this.pageService = pageService;
this.resourceService = resourceService; this.resourceService = pageService.getResourceService();
this.i18nSupport = resourceService.getI18nSupport(); this.i18nSupport = this.resourceService.getI18nSupport();
this.widgetFactory = pageService.getWidgetFactory(); this.widgetFactory = pageService.getWidgetFactory();
this.pageSize = pageSize; this.pageSize = pageSize;
@ -133,7 +134,7 @@ public class UserActivityLogs implements TemplateComposer {
.withColumn(new ColumnDefinition<UserActivityLog>( .withColumn(new ColumnDefinition<UserActivityLog>(
Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID, Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID,
ENTITY_TEXT_KEY, ENTITY_TYPE_TEXT_KEY,
this.resourceService::getEntityTypeName) this.resourceService::getEntityTypeName)
.withFilter(this.entityFilter) .withFilter(this.entityFilter)
.sortable()) .sortable())
@ -156,7 +157,7 @@ public class UserActivityLogs implements TemplateComposer {
.noEventPropagation() .noEventPropagation()
.create()) .create())
.compose(content); .compose(pageContext.copyOf(content));
actionBuilder actionBuilder
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS) .newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS)
@ -185,6 +186,7 @@ public class UserActivityLogs implements TemplateComposer {
action.pageContext().getParent().getShell(), action.pageContext().getParent().getShell(),
this.widgetFactory); this.widgetFactory);
//dialog.setDialogHeight(400);
dialog.open( dialog.open(
DETAILS_TITLE_TEXT_KEY, DETAILS_TITLE_TEXT_KEY,
action.pageContext(), action.pageContext(),
@ -194,7 +196,11 @@ public class UserActivityLogs implements TemplateComposer {
} }
private void createDetailsForm(final UserActivityLog userActivityLog, final PageContext pc) { private void createDetailsForm(final UserActivityLog userActivityLog, final PageContext pc) {
this.pageService.formBuilder(pc, 3)
final Composite parent = pc.getParent();
final Composite grid = this.widgetFactory.createPopupScrollComposite(parent);
this.pageService.formBuilder(pc.copyOf(grid), 3)
.withEmptyCellSeparation(false) .withEmptyCellSeparation(false)
.readonly(true) .readonly(true)
.addField(FormBuilder.text( .addField(FormBuilder.text(
@ -206,9 +212,13 @@ public class UserActivityLogs implements TemplateComposer {
ACTIVITY_TEXT_KEY, ACTIVITY_TEXT_KEY,
this.resourceService.getUserActivityTypeName(userActivityLog))) this.resourceService.getUserActivityTypeName(userActivityLog)))
.addField(FormBuilder.text( .addField(FormBuilder.text(
Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID, Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_TYPE,
ENTITY_TEXT_KEY, ENTITY_TYPE_TEXT_KEY,
this.resourceService.getEntityTypeName(userActivityLog))) this.resourceService.getEntityTypeName(userActivityLog)))
.addField(FormBuilder.text(
Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID,
ENTITY_ID_TEXT_KEY,
userActivityLog.entityId))
.addField(FormBuilder.text( .addField(FormBuilder.text(
Domain.USER_ACTIVITY_LOG.ATTR_TIMESTAMP, Domain.USER_ACTIVITY_LOG.ATTR_TIMESTAMP,
DATE_TEXT_KEY, DATE_TEXT_KEY,
@ -217,10 +227,9 @@ public class UserActivityLogs implements TemplateComposer {
.addField(FormBuilder.text( .addField(FormBuilder.text(
Domain.USER_ACTIVITY_LOG.ATTR_MESSAGE, Domain.USER_ACTIVITY_LOG.ATTR_MESSAGE,
MESSAGE_TEXT_KEY, MESSAGE_TEXT_KEY,
String.valueOf(userActivityLog.message).replace(",", ",\n")) String.valueOf(userActivityLog.message))
.asArea()) .asArea())
.build(); .build();
this.widgetFactory.labelSeparator(pc.getParent());
} }
} }

View file

@ -235,10 +235,9 @@ public class ActivitiesPane implements TemplateComposer {
final boolean viewUserActivityLogs = this.currentUser.hasInstitutionalPrivilege( final boolean viewUserActivityLogs = this.currentUser.hasInstitutionalPrivilege(
PrivilegeType.READ, PrivilegeType.READ,
EntityType.USER_ACTIVITY_LOG); EntityType.USER_ACTIVITY_LOG);
final boolean viewSebClientLogs = false; final boolean viewSebClientLogs = this.currentUser.hasInstitutionalPrivilege(
// this.currentUser.hasInstitutionalPrivilege( PrivilegeType.READ,
// PrivilegeType.READ, EntityType.EXAM);
// EntityType.EXAM);
TreeItem logRoot = null; TreeItem logRoot = null;
if (viewUserActivityLogs && viewSebClientLogs) { if (viewUserActivityLogs && viewSebClientLogs) {
@ -384,6 +383,9 @@ public class ActivitiesPane implements TemplateComposer {
} }
static final TreeItem findItemByActionDefinition(final TreeItem[] items, final PageState pageState) { static final TreeItem findItemByActionDefinition(final TreeItem[] items, final PageState pageState) {
if (pageState == null) {
return null;
}
return findItemByActionDefinition(items, pageState.activityAnchor(), null); return findItemByActionDefinition(items, pageState.activityAnchor(), null);
} }

View file

@ -200,6 +200,11 @@ public class FormBuilder {
return new TextFieldBuilder(name, label, value); return new TextFieldBuilder(name, label, value);
} }
public static TextFieldBuilder text(final String name, final LocTextKey label,
final Supplier<String> valueSupplier) {
return new TextFieldBuilder(name, label, valueSupplier.get());
}
public static SelectionFieldBuilder singleSelection( public static SelectionFieldBuilder singleSelection(
final String name, final String name,
final LocTextKey label, final LocTextKey label,

View file

@ -63,9 +63,9 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
? builder.widgetFactory.textAreaInput(fieldGrid) ? builder.widgetFactory.textAreaInput(fieldGrid)
: builder.widgetFactory.textInput(fieldGrid, this.isPassword); : builder.widgetFactory.textInput(fieldGrid, this.isPassword);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
if (this.isArea && !readonly) { if (this.isArea) {
gridData.heightHint = 50; gridData.minimumHeight = 35;
} }
textInput.setLayoutData(gridData); textInput.setLayoutData(gridData);
if (StringUtils.isNoneBlank(this.value)) { if (StringUtils.isNoneBlank(this.value)) {

View file

@ -17,6 +17,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -37,6 +38,8 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; 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.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
@ -49,6 +52,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.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
@ -75,6 +79,9 @@ public class ResourceService {
EntityType.USER_ROLE, EntityType.USER_ROLE,
EntityType.WEBSERVICE_SERVER_INFO); EntityType.WEBSERVICE_SERVER_INFO);
public static final EnumSet<EventType> CLIENT_EVENT_TYPE_EXCLUDE_MAP = EnumSet.of(
EventType.LAST_PING);
public static final String EXAMCONFIG_STATUS_PREFIX = "sebserver.examconfig.status."; public static final String EXAMCONFIG_STATUS_PREFIX = "sebserver.examconfig.status.";
public static final String EXAM_TYPE_PREFIX = "sebserver.exam.type."; public static final String EXAM_TYPE_PREFIX = "sebserver.exam.type.";
public static final String USERACCOUNT_ROLE_PREFIX = "sebserver.useraccount.role."; public static final String USERACCOUNT_ROLE_PREFIX = "sebserver.useraccount.role.";
@ -83,6 +90,7 @@ public class ResourceService {
public static final String CLIENT_EVENT_TYPE_PREFIX = "sebserver.monitoring.exam.connection.event.type."; public static final String CLIENT_EVENT_TYPE_PREFIX = "sebserver.monitoring.exam.connection.event.type.";
public static final String USER_ACTIVITY_TYPE_PREFIX = "sebserver.overall.types.activityType."; public static final String USER_ACTIVITY_TYPE_PREFIX = "sebserver.overall.types.activityType.";
public static final String ENTITY_TYPE_PREFIX = "sebserver.overall.types.entityType."; public static final String ENTITY_TYPE_PREFIX = "sebserver.overall.types.entityType.";
public static final String SEB_CONNECTION_STATUS_KEY_PREFIX = "sebserver.monitoring.exam.connection.status.";
public static final LocTextKey ACTIVE_TEXT_KEY = new LocTextKey("sebserver.overall.status.active"); public static final LocTextKey ACTIVE_TEXT_KEY = new LocTextKey("sebserver.overall.status.active");
public static final LocTextKey INACTIVE_TEXT_KEY = new LocTextKey("sebserver.overall.status.inactive"); public static final LocTextKey INACTIVE_TEXT_KEY = new LocTextKey("sebserver.overall.status.inactive");
@ -138,6 +146,7 @@ public class ResourceService {
public List<Tuple<String>> clientEventTypeResources() { public List<Tuple<String>> clientEventTypeResources() {
return Arrays.asList(EventType.values()) return Arrays.asList(EventType.values())
.stream() .stream()
.filter(Predicate.not(CLIENT_EVENT_TYPE_EXCLUDE_MAP::contains))
.map(eventType -> new Tuple<>( .map(eventType -> new Tuple<>(
eventType.name(), eventType.name(),
getEventTypeName(eventType))) getEventTypeName(eventType)))
@ -145,6 +154,13 @@ public class ResourceService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public String getEventTypeName(final ClientEvent event) {
if (event == null) {
return getEventTypeName(EventType.UNKNOWN);
}
return getEventTypeName(event.eventType);
}
public String getEventTypeName(final EventType eventType) { public String getEventTypeName(final EventType eventType) {
if (eventType == null) { if (eventType == null) {
return Constants.EMPTY_NOTE; return Constants.EMPTY_NOTE;
@ -384,6 +400,51 @@ public class ResourceService {
.getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name()); .getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name());
} }
public String localizedClientConnectionStatusName(final ConnectionStatus status) {
String name;
if (status != null) {
name = status.name();
} else {
name = ConnectionStatus.UNDEFINED.name();
}
return this.i18nSupport
.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + name, name);
}
public String localizedExamTypeName(final Exam exam) {
if (exam.type == null) {
return Constants.EMPTY_NOTE;
}
return this.i18nSupport
.getText(ResourceService.EXAM_TYPE_PREFIX + exam.type.name());
}
public List<Tuple<String>> getExamResources() {
final UserInfo userInfo = this.currentUser.get();
return this.restService.getBuilder(GetExamNames.class)
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.getInstitutionId()))
.call()
.getOr(Collections.emptyList())
.stream()
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
public Map<Long, String> getExamNameMapping() {
final UserInfo userInfo = this.currentUser.get();
return this.restService.getBuilder(GetExamNames.class)
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.getInstitutionId()))
.call()
.getOr(Collections.emptyList())
.stream()
.filter(k -> StringUtils.isNotBlank(k.modelId))
.collect(Collectors.toMap(
k -> Long.valueOf(k.modelId),
k -> k.name));
}
private Result<List<EntityName>> getExamConfigurationSelection() { private Result<List<EntityName>> getExamConfigurationSelection() {
return this.restService.getBuilder(GetExamConfigMappingNames.class) return this.restService.getBuilder(GetExamConfigMappingNames.class)
.withQueryParam( .withQueryParam(
@ -398,13 +459,4 @@ public class ResourceService {
.call(); .call();
} }
public String examTypeName(final Exam exam) {
if (exam.type == null) {
return Constants.EMPTY_NOTE;
}
return this.i18nSupport
.getText(ResourceService.EXAM_TYPE_PREFIX + exam.type.name());
}
} }

View file

@ -13,6 +13,8 @@ import java.util.Locale;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import ch.ethz.seb.sebserver.gbl.util.Utils;
public interface I18nSupport { public interface I18nSupport {
/** Get all supported languages as a collection of Locale /** Get all supported languages as a collection of Locale
@ -38,6 +40,18 @@ public interface I18nSupport {
* @return date formatted date String to display */ * @return date formatted date String to display */
String formatDisplayDate(DateTime date); String formatDisplayDate(DateTime date);
/** Format a time-stamp (milliseconds) to a text format to display.
* This uses the date-format defined by either the attribute 'sebserver.gui.date.displayformat'
* or the Constants.DEFAULT_DISPLAY_DATE_FORMAT
*
* Adds time-zone information if the currents user time-zone is different form UTC
*
* @param date the DateTime instance
* @return date formatted date String to display */
default String formatDisplayDate(final Long timestamp) {
return formatDisplayDate(Utils.toDateTimeUTC(timestamp));
}
/** Format a DateTime to a text format to display. /** Format a DateTime to a text format to display.
* This uses the date-format defined by either the attribute 'sebserver.gui.datetime.displayformat' * This uses the date-format defined by either the attribute 'sebserver.gui.datetime.displayformat'
* or the Constants.DEFAULT_DISPLAY_DATE_TIME_FORMAT * or the Constants.DEFAULT_DISPLAY_DATE_TIME_FORMAT
@ -48,6 +62,18 @@ public interface I18nSupport {
* @return date formatted date time String to display */ * @return date formatted date time String to display */
String formatDisplayDateTime(DateTime date); String formatDisplayDateTime(DateTime date);
/** Format a time-stamp (milliseconds) to a text format to display.
* This uses the date-format defined by either the attribute 'sebserver.gui.datetime.displayformat'
* or the Constants.DEFAULT_DISPLAY_DATE_TIME_FORMAT
*
* Adds time-zone information if the currents user time-zone is different form UTC
*
* @param date the DateTime instance
* @return date formatted date time String to display */
default String formatDisplayDateTime(final Long timestamp) {
return formatDisplayDateTime(Utils.toDateTimeUTC(timestamp));
}
/** Format a DateTime to a text format to display. /** Format a DateTime to a text format to display.
* This uses the date-format defined by either the attribute 'sebserver.gui.time.displayformat' * This uses the date-format defined by either the attribute 'sebserver.gui.time.displayformat'
* or the Constants.DEFAULT_DISPLAY_TIME_FORMAT * or the Constants.DEFAULT_DISPLAY_TIME_FORMAT
@ -58,6 +84,18 @@ public interface I18nSupport {
* @return date formatted time String to display */ * @return date formatted time String to display */
String formatDisplayTime(DateTime date); String formatDisplayTime(DateTime date);
/** Format a time-stamp (milliseconds) to a text format to display.
* This uses the date-format defined by either the attribute 'sebserver.gui.time.displayformat'
* or the Constants.DEFAULT_DISPLAY_TIME_FORMAT
*
* Adds time-zone information if the currents user time-zone is different form UTC
*
* @param date the DateTime instance
* @return date formatted time String to display */
default String formatDisplayTime(final Long timestamp) {
return formatDisplayTime(Utils.toDateTimeUTC(timestamp));
}
/** If the current user has another time zone then UTC this will return a tile suffix that describes /** If the current user has another time zone then UTC this will return a tile suffix that describes
* a date/time column title with adding (UTC|{usersTimeZone}) that can be added to the title. * a date/time column title with adding (UTC|{usersTimeZone}) that can be added to the title.
* *

View file

@ -121,14 +121,14 @@ public interface PageService {
* @param pageContext the PageContext on that the FormBuilder should work * @param pageContext the PageContext on that the FormBuilder should work
* @param rows the number of rows of the from * @param rows the number of rows of the from
* @return a FormBuilder instance for the given PageContext and with number of rows */ * @return a FormBuilder instance for the given PageContext and with number of rows */
FormBuilder formBuilder(final PageContext pageContext, final int rows); FormBuilder formBuilder(PageContext pageContext, int rows);
/** Get an new TableBuilder for specified page based RestCall. /** Get an new TableBuilder for specified page based RestCall.
* *
* @param apiCall the SEB Server API RestCall that feeds the table with data * @param apiCall the SEB Server API RestCall that feeds the table with data
* @param <T> the type of the Entity of the table * @param <T> the type of the Entity of the table
* @return TableBuilder of specified type */ * @return TableBuilder of specified type */
<T extends Entity> TableBuilder<T> entityTableBuilder(final RestCall<Page<T>> apiCall); <T extends Entity> TableBuilder<T> entityTableBuilder(RestCall<Page<T>> apiCall);
/** Get a new PageActionBuilder for a given PageContext. /** Get a new PageActionBuilder for a given PageContext.
* *

View file

@ -13,7 +13,6 @@ import java.util.function.Supplier;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
@ -42,6 +41,7 @@ public class ModalInputDialog<T> extends Dialog {
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private int dialogWidth = 400; private int dialogWidth = 400;
private int dialogHeight = 600; private int dialogHeight = 600;
private final int buttonWidth = 100;
public ModalInputDialog( public ModalInputDialog(
final Shell parent, final Shell parent,
@ -75,25 +75,21 @@ public class ModalInputDialog<T> extends Dialog {
shell.setText(this.widgetFactory.getI18nSupport().getText(title)); shell.setText(this.widgetFactory.getI18nSupport().getText(title));
shell.setLayout(new GridLayout(2, true)); shell.setLayout(new GridLayout(2, true));
final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, false, false); final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, false, false);
gridData2.widthHint = this.dialogWidth;
//gridData2.heightHint = this.dialogHeight;
shell.setLayoutData(gridData2); shell.setLayoutData(gridData2);
final Composite main = new Composite(shell, SWT.NONE); final Composite main = new Composite(shell, SWT.NONE);
main.setLayout(new GridLayout()); main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, false, false); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = 2; gridData.horizontalSpan = 2;
gridData.widthHint = this.dialogWidth; gridData.widthHint = this.dialogWidth;
main.setLayoutData(gridData); main.setLayoutData(gridData);
final Supplier<T> valueSuppier = contentComposer.compose(main); final Supplier<T> valueSuppier = contentComposer.compose(main);
gridData.heightHint = calcDialogHeight(main);
final Point computeSize = main.computeSize(SWT.DEFAULT, SWT.DEFAULT);
gridData.heightHint = (computeSize.y < this.dialogHeight) ? computeSize.y : this.dialogHeight;
final Button ok = this.widgetFactory.buttonLocalized(shell, OK_TEXT_KEY); final Button ok = this.widgetFactory.buttonLocalized(shell, OK_TEXT_KEY);
GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END); GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
data.widthHint = 100; data.widthHint = this.buttonWidth;
ok.setLayoutData(data); ok.setLayoutData(data);
ok.addListener(SWT.Selection, event -> { ok.addListener(SWT.Selection, event -> {
if (valueSuppier != null) { if (valueSuppier != null) {
@ -109,7 +105,7 @@ public class ModalInputDialog<T> extends Dialog {
final Button cancel = this.widgetFactory.buttonLocalized(shell, CANCEL_TEXT_KEY); final Button cancel = this.widgetFactory.buttonLocalized(shell, CANCEL_TEXT_KEY);
data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
data.widthHint = 100; data.widthHint = this.buttonWidth;
cancel.setLayoutData(data); cancel.setLayoutData(data);
cancel.addListener(SWT.Selection, event -> { cancel.addListener(SWT.Selection, event -> {
if (cancelCallback != null) { if (cancelCallback != null) {
@ -132,18 +128,22 @@ public class ModalInputDialog<T> extends Dialog {
shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key); shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key);
shell.setText(this.widgetFactory.getI18nSupport().getText(title)); shell.setText(this.widgetFactory.getI18nSupport().getText(title));
shell.setLayout(new GridLayout()); shell.setLayout(new GridLayout());
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, true, true);
shell.setLayoutData(gridData2);
final Composite main = new Composite(shell, SWT.NONE); final Composite main = new Composite(shell, SWT.NONE);
main.setLayout(new GridLayout()); main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
gridData.widthHint = this.dialogWidth;
main.setLayoutData(gridData); main.setLayoutData(gridData);
final PageContext internalPageContext = pageContext.copyOf(main);
contentComposer.accept(internalPageContext); contentComposer.accept(pageContext.copyOf(main));
gridData.heightHint = calcDialogHeight(main);
final Button close = this.widgetFactory.buttonLocalized(shell, CLOSE_TEXT_KEY); final Button close = this.widgetFactory.buttonLocalized(shell, CLOSE_TEXT_KEY);
final GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); final GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
data.widthHint = 100; data.widthHint = this.buttonWidth;
close.setLayoutData(data); close.setLayoutData(data);
close.addListener(SWT.Selection, event -> { close.addListener(SWT.Selection, event -> {
shell.close(); shell.close();
@ -164,4 +164,16 @@ public class ModalInputDialog<T> extends Dialog {
shell.open(); shell.open();
} }
private int calcDialogHeight(final Composite main) {
final int actualHeight = main.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
final int displayHeight = main.getDisplay().getClientArea().height;
final int availableHeight = (displayHeight < actualHeight + 100)
? displayHeight - 100
: actualHeight;
final int height = (availableHeight > this.dialogHeight)
? this.dialogHeight
: availableHeight;
return height;
}
} }

View file

@ -34,7 +34,8 @@ public class GetInstitution extends RestCall<Institution> {
}), }),
HttpMethod.GET, HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_FORM_URLENCODED,
API.INSTITUTION_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT); API.INSTITUTION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT);
} }
} }

View file

@ -0,0 +1,41 @@
/*
* 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.remote.webservice.api.logs;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetExtendedClientEventPage extends RestCall<Page<ExtendedClientEvent>> {
public GetExtendedClientEventPage() {
super(new TypeKey<>(
CallType.GET_PAGE,
EntityType.CLIENT_EVENT,
new TypeReference<Page<ExtendedClientEvent>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.SEB_CLIENT_EVENT_EXTENDED_PAGE_ENDPOINT);
}
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.userlogs; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;

View file

@ -0,0 +1,41 @@
/*
* 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.remote.webservice.api.session;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetClientConnection extends RestCall<ClientConnection> {
public GetClientConnection() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.CLIENT_CONNECTION,
new TypeReference<ClientConnection>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.SEB_CLIENT_CONNECTION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT);
}
}

View file

@ -36,7 +36,8 @@ public class GetClientConnectionDataList extends RestCall<Collection<ClientConne
}), }),
HttpMethod.GET, HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT + API.EXAM_MONITORING_EXAM_ID_PATH_SEGMENT); API.EXAM_MONITORING_ENDPOINT
+ API.EXAM_MONITORING_EXAM_ID_PATH_SEGMENT);
} }
} }

View file

@ -36,8 +36,6 @@ public class ClientConnectionDetails {
private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class); private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class);
private final static String STATUS_LOC_TEXT_KEY_PREFIX = "sebserver.monitoring.connection.status.";
private final static LocTextKey EXAM_NAME_TEXT_KEY = private final static LocTextKey EXAM_NAME_TEXT_KEY =
new LocTextKey("sebserver.monitoring.connection.list.column.examname"); new LocTextKey("sebserver.monitoring.connection.list.column.examname");
private final static LocTextKey CONNECTION_ID_TEXT_KEY = private final static LocTextKey CONNECTION_ID_TEXT_KEY =
@ -163,14 +161,10 @@ public class ClientConnectionDetails {
} }
String getStatusName() { String getStatusName() {
String name; return this.pageService.getResourceService().localizedClientConnectionStatusName(
if (this.connectionData != null && this.connectionData.clientConnection.status != null) { (this.connectionData != null && this.connectionData.clientConnection != null)
name = this.connectionData.clientConnection.status.name(); ? this.connectionData.clientConnection.status
} else { : ConnectionStatus.UNDEFINED);
name = ConnectionStatus.UNDEFINED.name();
}
return this.pageService.getI18nSupport()
.getText(STATUS_LOC_TEXT_KEY_PREFIX + name, name);
} }
} }

View file

@ -122,9 +122,10 @@ public final class ColumnDefinition<ROW extends Entity> {
public final String columnName; public final String columnName;
public final String initValue; public final String initValue;
public final Supplier<List<Tuple<String>>> resourceSupplier; public final Supplier<List<Tuple<String>>> resourceSupplier;
public final Function<EntityTable<?>, List<Tuple<String>>> resourceFunction;
public TableFilterAttribute(final CriteriaType type, final String columnName) { public TableFilterAttribute(final CriteriaType type, final String columnName) {
this(type, columnName, "", null); this(type, columnName, "", (Supplier<List<Tuple<String>>>) null);
} }
public TableFilterAttribute( public TableFilterAttribute(
@ -140,7 +141,7 @@ public final class ColumnDefinition<ROW extends Entity> {
final String columnName, final String columnName,
final String initValue) { final String initValue) {
this(type, columnName, initValue, null); this(type, columnName, initValue, (Supplier<List<Tuple<String>>>) null);
} }
public TableFilterAttribute( public TableFilterAttribute(
@ -153,6 +154,20 @@ public final class ColumnDefinition<ROW extends Entity> {
this.columnName = columnName; this.columnName = columnName;
this.initValue = initValue; this.initValue = initValue;
this.resourceSupplier = resourceSupplier; this.resourceSupplier = resourceSupplier;
this.resourceFunction = null;
}
public TableFilterAttribute(
final CriteriaType type,
final String columnName,
final String initValue,
final Function<EntityTable<?>, List<Tuple<String>>> resourceFunction) {
this.type = type;
this.columnName = columnName;
this.initValue = initValue;
this.resourceSupplier = null;
this.resourceFunction = resourceFunction;
} }
} }

View file

@ -46,6 +46,7 @@ import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.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.PageMessageException;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
@ -66,6 +67,7 @@ public class EntityTable<ROW extends Entity> {
final RestCall<Page<ROW>> restCall; final RestCall<Page<ROW>> restCall;
final Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter; final Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter;
final I18nSupport i18nSupport; final I18nSupport i18nSupport;
final PageContext pageContext;
final List<ColumnDefinition<ROW>> columns; final List<ColumnDefinition<ROW>> columns;
final LocTextKey emptyMessage; final LocTextKey emptyMessage;
@ -84,7 +86,7 @@ public class EntityTable<ROW extends Entity> {
EntityTable( EntityTable(
final int type, final int type,
final Composite parent, final PageContext pageContext,
final RestCall<Page<ROW>> restCall, final RestCall<Page<ROW>> restCall,
final Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter, final Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter,
final PageService pageService, final PageService pageService,
@ -94,9 +96,10 @@ public class EntityTable<ROW extends Entity> {
final Function<EntityTable<ROW>, PageAction> defaultActionFunction, final Function<EntityTable<ROW>, PageAction> defaultActionFunction,
final boolean hideNavigation) { final boolean hideNavigation) {
this.composite = new Composite(parent, type); this.composite = new Composite(pageContext.getParent(), type);
this.pageService = pageService; this.pageService = pageService;
this.i18nSupport = pageService.getI18nSupport(); this.i18nSupport = pageService.getI18nSupport();
this.pageContext = pageContext;
this.widgetFactory = pageService.getWidgetFactory(); this.widgetFactory = pageService.getWidgetFactory();
this.restCall = restCall; this.restCall = restCall;
this.restCallAdapter = (restCallAdapter != null) ? restCallAdapter : Function.identity(); this.restCallAdapter = (restCallAdapter != null) ? restCallAdapter : Function.identity();
@ -365,9 +368,7 @@ public class EntityTable<ROW extends Entity> {
.call() .call()
.map(this::createTableRowsFromPage) .map(this::createTableRowsFromPage)
.map(this.navigator::update) .map(this.navigator::update)
.onError(t -> { .onError(this.pageContext::notifyError);
// TODO error handling
});
this.composite.getParent().layout(true, true); this.composite.getParent().layout(true, true);
PageService.updateScrolledComposite(this.composite); PageService.updateScrolledComposite(this.composite);

View file

@ -15,11 +15,11 @@ import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@ -106,10 +106,10 @@ public class TableBuilder<ROW extends Entity> {
return this; return this;
} }
public EntityTable<ROW> compose(final Composite parent) { public EntityTable<ROW> compose(final PageContext pageContext) {
return new EntityTable<>( return new EntityTable<>(
this.type, this.type,
parent, pageContext,
this.restCall, this.restCall,
this.restCallAdapter, this.restCallAdapter,
this.pageService, this.pageService,

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -29,6 +30,7 @@ import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.widget.Selection; import ch.ethz.seb.sebserver.gui.widget.Selection;
@ -302,11 +304,17 @@ public class TableFilter<ROW extends Entity> {
final Composite innerComposite = createInnerComposite(parent); final Composite innerComposite = createInnerComposite(parent);
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
Supplier<List<Tuple<String>>> resourceSupplier = this.attribute.resourceSupplier;
if (this.attribute.resourceFunction != null) {
resourceSupplier = () -> this.attribute.resourceFunction.apply(TableFilter.this.entityTable);
}
this.selector = TableFilter.this.entityTable.widgetFactory this.selector = TableFilter.this.entityTable.widgetFactory
.selectionLocalized( .selectionLocalized(
ch.ethz.seb.sebserver.gui.widget.Selection.Type.SINGLE, ch.ethz.seb.sebserver.gui.widget.Selection.Type.SINGLE,
innerComposite, innerComposite,
this.attribute.resourceSupplier); resourceSupplier);
this.selector this.selector
.adaptToControl() .adaptToControl()
.setLayoutData(gridData); .setLayoutData(gridData);

View file

@ -47,6 +47,7 @@ import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
@Lazy @Lazy
@ -196,6 +197,23 @@ public class WidgetFactory {
return grid; return grid;
} }
/** Use this to create a scrolled Composite for usual popup forms
*
* @param parent The parent Composite
* @return the scrolled Composite to add the form content */
public Composite createPopupScrollComposite(final Composite parent) {
final Composite grid = PageService.createManagedVScrolledComposite(
parent,
scrolledComposite -> {
final Composite g = new Composite(scrolledComposite, SWT.NONE);
g.setLayout(new GridLayout());
g.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
return g;
},
false);
return grid;
}
public Button buttonLocalized(final Composite parent, final String locTextKey) { public Button buttonLocalized(final Composite parent, final String locTextKey) {
final Button button = new Button(parent, SWT.NONE); final Button button = new Button(parent, SWT.NONE);
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey)); this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));

View file

@ -8,8 +8,16 @@
package ch.ethz.seb.sebserver.webservice.datalayer.batis; package ch.ethz.seb.sebserver.webservice.datalayer.batis;
import static org.mybatis.dynamic.sql.SqlBuilder.equalTo;
import java.util.Collection;
import org.apache.ibatis.annotations.Arg;
import org.apache.ibatis.annotations.ConstructorArgs;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.ResultType;
import org.apache.ibatis.annotations.SelectProvider; import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter; import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter;
@ -18,6 +26,7 @@ import org.mybatis.dynamic.sql.select.SelectDSL;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter; import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
@Mapper @Mapper
@ -36,4 +45,86 @@ public interface ClientEventExtentionMapper {
.from(ClientEventRecordDynamicSqlSupport.clientEventRecord); .from(ClientEventRecordDynamicSqlSupport.clientEventRecord);
} }
@SelectProvider(type = SqlProviderAdapter.class, method = "select")
@ResultType(ClientEventExtentionMapper.ConnectionEventJoinRecord.class)
@ConstructorArgs({
@Arg(column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT, id = true),
@Arg(column = "connection_id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Arg(column = "type", javaType = Integer.class, jdbcType = JdbcType.INTEGER),
@Arg(column = "client_time", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Arg(column = "server_time", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Arg(column = "numeric_value", javaType = Double.class, jdbcType = JdbcType.DECIMAL),
@Arg(column = "text", javaType = String.class, jdbcType = JdbcType.VARCHAR),
@Arg(column = "institution_id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Arg(column = "exam_id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Arg(column = "exam_user_session_identifer", javaType = String.class, jdbcType = JdbcType.VARCHAR)
})
Collection<ConnectionEventJoinRecord> selectMany(SelectStatementProvider select);
default QueryExpressionDSL<MyBatis3SelectModelAdapter<Collection<ConnectionEventJoinRecord>>>.JoinSpecificationFinisher selectByExample() {
return SelectDSL.selectWithMapper(
this::selectMany,
ClientEventRecordDynamicSqlSupport.id,
ClientEventRecordDynamicSqlSupport.connectionId.as("connection_id"),
ClientEventRecordDynamicSqlSupport.type,
ClientEventRecordDynamicSqlSupport.clientTime.as("client_time"),
ClientEventRecordDynamicSqlSupport.serverTime.as("server_time"),
ClientEventRecordDynamicSqlSupport.numericValue.as("numeric_value"),
ClientEventRecordDynamicSqlSupport.text,
ClientConnectionRecordDynamicSqlSupport.institutionId.as("institution_id"),
ClientConnectionRecordDynamicSqlSupport.examId.as("exam_id"),
ClientConnectionRecordDynamicSqlSupport.examUserSessionIdentifer.as("exam_user_session_identifer"))
.from(ClientEventRecordDynamicSqlSupport.clientEventRecord)
.leftJoin(ClientConnectionRecordDynamicSqlSupport.clientConnectionRecord)
.on(
ClientEventRecordDynamicSqlSupport.clientEventRecord.connectionId,
equalTo(ClientConnectionRecordDynamicSqlSupport.clientConnectionRecord.id));
}
public static final class ConnectionEventJoinRecord {
public final Long id;
public final Long connection_id;
public final Integer type;
public final Long client_time;
public final Long server_time;
public final Double numeric_value;
public final String text;
public final Long institution_id;
public final Long exam_id;
public final String exam_user_session_identifer;
protected ConnectionEventJoinRecord(
final Long id,
final Long connection_id,
final Integer type,
final Long client_time,
final Long server_time,
final Double numeric_value,
final String text,
final Long institution_id,
final Long exam_id,
final String exam_user_session_identifer) {
this.id = id;
this.connection_id = connection_id;
this.type = type;
this.client_time = client_time;
this.server_time = server_time;
this.numeric_value = numeric_value;
this.text = text;
this.institution_id = institution_id;
this.exam_id = exam_id;
this.exam_user_session_identifer = exam_user_session_identifer;
}
}
} }

View file

@ -37,7 +37,7 @@ public interface PaginationService {
void setDefaultLimit(); void setDefaultLimit();
void setDefaultLimit(final String sort, final SqlTable table); void setDefaultLimit(final String sort, final String tableName);
int getPageNumber(final Integer pageNumber); int getPageNumber(final Integer pageNumber);

View file

@ -94,8 +94,8 @@ public class PaginationServiceImpl implements PaginationService {
} }
@Override @Override
public void setDefaultLimit(final String sort, final SqlTable table) { public void setDefaultLimit(final String sort, final String tableName) {
setPagination(1, this.maxPageSize, sort, table); setPagination(1, this.maxPageSize, sort, tableName);
} }
@Override @Override
@ -131,9 +131,9 @@ public class PaginationServiceImpl implements PaginationService {
final Supplier<Result<Collection<T>>> delegate) { final Supplier<Result<Collection<T>>> delegate) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final SqlTable table = SqlTable.of(tableName); //final SqlTable table = SqlTable.of(tableName);
final com.github.pagehelper.Page<Object> page = final com.github.pagehelper.Page<Object> page =
setPagination(pageNumber, pageSize, sort, table); setPagination(pageNumber, pageSize, sort, tableName);
final Collection<T> list = delegate.get().getOrThrow(); final Collection<T> list = delegate.get().getOrThrow();
@ -145,36 +145,36 @@ public class PaginationServiceImpl implements PaginationService {
}); });
} }
private String verifySortColumnName(final String sort, final SqlTable table) { private String verifySortColumnName(final String sort, final String columnName) {
if (StringUtils.isBlank(sort)) { if (StringUtils.isBlank(sort)) {
return this.defaultSortColumn.get(table.name()); return this.defaultSortColumn.get(columnName);
} }
final Map<String, String> mapping = this.sortColumnMapping.get(table.name()); final Map<String, String> mapping = this.sortColumnMapping.get(columnName);
if (mapping != null) { if (mapping != null) {
final String sortColumn = PageSortOrder.decode(sort); final String sortColumn = PageSortOrder.decode(sort);
if (StringUtils.isBlank(sortColumn)) { if (StringUtils.isBlank(sortColumn)) {
return this.defaultSortColumn.get(table.name()); return this.defaultSortColumn.get(columnName);
} }
return mapping.get(sortColumn); return mapping.get(sortColumn);
} }
return this.defaultSortColumn.get(table.name()); return this.defaultSortColumn.get(columnName);
} }
private com.github.pagehelper.Page<Object> setPagination( private com.github.pagehelper.Page<Object> setPagination(
final Integer pageNumber, final Integer pageNumber,
final Integer pageSize, final Integer pageSize,
final String sort, final String sort,
final SqlTable table) { final String sortMappingName) {
final com.github.pagehelper.Page<Object> startPage = final com.github.pagehelper.Page<Object> startPage =
PageHelper.startPage(getPageNumber(pageNumber), getPageSize(pageSize), true, true, false); PageHelper.startPage(getPageNumber(pageNumber), getPageSize(pageSize), true, true, false);
if (table != null && StringUtils.isNotBlank(sort)) { if (StringUtils.isNotBlank(sortMappingName) && StringUtils.isNotBlank(sort)) {
final PageSortOrder sortOrder = PageSortOrder.getSortOrder(sort); final PageSortOrder sortOrder = PageSortOrder.getSortOrder(sort);
final String sortColumnName = verifySortColumnName(sort, table); final String sortColumnName = verifySortColumnName(sort, sortMappingName);
if (StringUtils.isNotBlank(sortColumnName)) { if (StringUtils.isNotBlank(sortColumnName)) {
switch (sortOrder) { switch (sortOrder) {
case DESCENDING: { case DESCENDING: {

View file

@ -8,7 +8,10 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization; package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set; import java.util.Set;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
@ -233,17 +236,21 @@ public interface AuthorizationService {
/** Checks if the current user has a specified role. /** Checks if the current user has a specified role.
* If not a PermissionDeniedException is thrown for the given EntityType * If not a PermissionDeniedException is thrown for the given EntityType
* *
* @param role The UserRole to check * @param roles The UserRoles to check if any of them matches to current users roles
* @param institution the institution identifier * @param institution the institution identifier
* @param type EntityType for PermissionDeniedException * @param type EntityType for PermissionDeniedException
* @throws PermissionDeniedException if current user don't have the specified UserRole */ * @throws PermissionDeniedException if current user don't have the specified UserRole */
default void checkRole(final UserRole role, final Long institution, final EntityType type) { default void checkRole(final Long institution, final EntityType type, final UserRole... roles) {
final SEBServerUser currentUser = this final SEBServerUser currentUser = this
.getUserService() .getUserService()
.getCurrentUser(); .getCurrentUser();
final List<UserRole> rolesList = (roles == null)
? Collections.emptyList()
: Arrays.asList(roles);
if (!currentUser.institutionId().equals(institution) || if (!currentUser.institutionId().equals(institution) ||
!currentUser.getUserRoles().contains(role)) { Collections.disjoint(currentUser.getUserRoles(), rolesList)) {
throw new PermissionDeniedException( throw new PermissionDeniedException(
type, type,

View file

@ -19,7 +19,10 @@ import java.util.stream.Collectors;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
@ -34,11 +37,13 @@ public class BulkActionServiceImpl implements BulkActionService {
private final Map<EntityType, BulkActionSupportDAO<?>> supporter; private final Map<EntityType, BulkActionSupportDAO<?>> supporter;
private final UserActivityLogDAO userActivityLogDAO; private final UserActivityLogDAO userActivityLogDAO;
private final ApplicationEventPublisher applicationEventPublisher; private final ApplicationEventPublisher applicationEventPublisher;
private final JSONMapper jsonMapper;
public BulkActionServiceImpl( public BulkActionServiceImpl(
final Collection<BulkActionSupportDAO<?>> supporter, final Collection<BulkActionSupportDAO<?>> supporter,
final UserActivityLogDAO userActivityLogDAO, final UserActivityLogDAO userActivityLogDAO,
final ApplicationEventPublisher applicationEventPublisher) { final ApplicationEventPublisher applicationEventPublisher,
final JSONMapper jsonMapper) {
this.supporter = new HashMap<>(); this.supporter = new HashMap<>();
for (final BulkActionSupportDAO<?> support : supporter) { for (final BulkActionSupportDAO<?> support : supporter) {
@ -46,6 +51,7 @@ public class BulkActionServiceImpl implements BulkActionService {
} }
this.userActivityLogDAO = userActivityLogDAO; this.userActivityLogDAO = userActivityLogDAO;
this.applicationEventPublisher = applicationEventPublisher; this.applicationEventPublisher = applicationEventPublisher;
this.jsonMapper = jsonMapper;
} }
@Override @Override
@ -123,11 +129,12 @@ public class BulkActionServiceImpl implements BulkActionService {
} }
for (final EntityKey key : action.dependencies) { for (final EntityKey key : action.dependencies) {
this.userActivityLogDAO.log( this.userActivityLogDAO.log(
activityType, activityType,
key.entityType, key.entityType,
key.modelId, key.modelId,
"bulk action dependency"); "Bulk Action - Dependency : " + toLogMessage(key));
} }
for (final EntityKey key : action.sources) { for (final EntityKey key : action.sources) {
@ -135,10 +142,22 @@ public class BulkActionServiceImpl implements BulkActionService {
activityType, activityType,
key.entityType, key.entityType,
key.modelId, key.modelId,
"bulk action source"); "Bulk Action - Source : " + toLogMessage(key));
} }
} }
private String toLogMessage(final EntityKey key) {
String entityAsString;
try {
entityAsString = this.jsonMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(key);
} catch (final JsonProcessingException e) {
entityAsString = key.toString();
}
return entityAsString;
}
private List<BulkActionSupportDAO<?>> getDependancySupporter(final BulkAction action) { private List<BulkActionSupportDAO<?>> getDependancySupporter(final BulkAction action) {
switch (action.type) { switch (action.type) {
case ACTIVATE: case ACTIVATE:

View file

@ -8,8 +8,17 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao; package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection;
import java.util.function.Predicate;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.util.Result;
public interface ClientEventDAO extends EntityDAO<ClientEvent, ClientEvent> { public interface ClientEventDAO extends EntityDAO<ClientEvent, ClientEvent> {
Result<Collection<ExtendedClientEvent>> allMatchingExtended(
FilterMap filterMap,
Predicate<ExtendedClientEvent> predicate);
} }

View file

@ -88,8 +88,8 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
.getOrThrow() .getOrThrow()
.stream() .stream()
.map(entity -> new EntityName( .map(entity -> new EntityName(
entity.entityType(),
entity.getModelId(), entity.getModelId(),
entity.entityType(),
entity.getName())) entity.getName()))
.collect(Collectors.toList()); .collect(Collectors.toList());
}); });

View file

@ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -235,14 +236,14 @@ public class FilterMap extends POSTMapper {
false); false);
} }
public Long getUserLogFrom(final String filterAttrFrom) { public Long getUserLogFrom() {
return getFromToValue( return getFromToValue(
UserActivityLog.FILTER_ATTR_FROM_TO, UserActivityLog.FILTER_ATTR_FROM_TO,
UserActivityLog.FILTER_ATTR_FROM, UserActivityLog.FILTER_ATTR_FROM,
true); true);
} }
public Long getUserLofTo(final String filterAttrTo) { public Long getUserLofTo() {
return getFromToValue( return getFromToValue(
UserActivityLog.FILTER_ATTR_FROM_TO, UserActivityLog.FILTER_ATTR_FROM_TO,
UserActivityLog.FILTER_ATTR_TO, UserActivityLog.FILTER_ATTR_TO,
@ -253,6 +254,10 @@ public class FilterMap extends POSTMapper {
return getSQLWildcard(ClientEvent.FILTER_ATTR_TEXT); return getSQLWildcard(ClientEvent.FILTER_ATTR_TEXT);
} }
public Long getClientEventExamId() {
return getLong(ExtendedClientEvent.FILTER_ATTRIBUTE_EXAM);
}
private Long getFromToValue(final String nameCombi, final String name, final boolean from) { private Long getFromToValue(final String nameCombi, final String name, final boolean from) {
final Long value = getFromToValue(nameCombi, from); final Long value = getFromToValue(nameCombi, from);
if (value != null) { if (value != null) {

View file

@ -29,6 +29,10 @@ public interface UserActivityLogDAO extends
* @return Result of the Entity or referring to an Error id happened */ * @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logCreate(E entity); public <E extends Entity> Result<E> logCreate(E entity);
public <E extends Entity> Result<E> logSaveToHistory(E entity);
public <E extends Entity> Result<E> logUndo(E entity);
/** Create a user activity log entry for the current user of activity type IMPORT /** Create a user activity log entry for the current user of activity type IMPORT
* *
* @param entity the Entity * @param entity the Entity
@ -47,24 +51,6 @@ public interface UserActivityLogDAO extends
* @return Result of the Entity or referring to an Error id happened */ * @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logModify(E entity); public <E extends Entity> Result<E> logModify(E entity);
/** Create a user activity log entry for the current user of activity type ACTIVATE
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logActivate(E entity);
/** Create a user activity log entry for the current user of activity type DEACTIVATE
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logDeactivate(E entity);
/** Create a user activity log entry for the current user of activity type DELETE
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logDelete(E entity);
/** Creates a user activity log entry for the current user. /** Creates a user activity log entry for the current user.
* *
* @param activityType the activity type * @param activityType the activity type

View file

@ -25,10 +25,15 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper.ConnectionEventJoinRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
@ -44,9 +49,14 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
public class ClientEventDAOImpl implements ClientEventDAO { public class ClientEventDAOImpl implements ClientEventDAO {
private final ClientEventRecordMapper clientEventRecordMapper; private final ClientEventRecordMapper clientEventRecordMapper;
private final ClientEventExtentionMapper clientEventExtentionMapper;
protected ClientEventDAOImpl(
final ClientEventRecordMapper clientEventRecordMapper,
final ClientEventExtentionMapper clientEventExtentionMapper) {
protected ClientEventDAOImpl(final ClientEventRecordMapper clientEventRecordMapper) {
this.clientEventRecordMapper = clientEventRecordMapper; this.clientEventRecordMapper = clientEventRecordMapper;
this.clientEventExtentionMapper = clientEventExtentionMapper;
} }
@Override @Override
@ -100,7 +110,54 @@ public class ClientEventDAOImpl implements ClientEventDAO {
.flatMap(DAOLoggingSupport::logAndSkipOnError) .flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate) .filter(predicate)
.collect(Collectors.toList())); .collect(Collectors.toList()));
}
@Override
public Result<Collection<ExtendedClientEvent>> allMatchingExtended(
final FilterMap filterMap,
final Predicate<ExtendedClientEvent> predicate) {
return Result.tryCatch(() -> this.clientEventExtentionMapper.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getClientEventExamId()))
.and(
ClientConnectionRecordDynamicSqlSupport.examUserSessionIdentifer,
SqlBuilder.isLikeWhenPresent(filterMap.getSQLWildcard(ClientConnection.FILTER_ATTR_SESSION_ID)))
.and(
ClientEventRecordDynamicSqlSupport.connectionId,
isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
isEqualToWhenPresent(filterMap.getClientEventTypeId()))
.and(
ClientEventRecordDynamicSqlSupport.type,
SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
.and(
ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
.and(
ClientEventRecordDynamicSqlSupport.text,
SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
.build()
.execute()
.stream()
.map(ClientEventDAOImpl::toDomainModelExtended)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.collect(Collectors.toList()));
} }
@Override @Override
@ -183,7 +240,23 @@ public class ClientEventDAOImpl implements ClientEventDAO {
(numericValue != null) ? numericValue.doubleValue() : null, (numericValue != null) ? numericValue.doubleValue() : null,
record.getText()); record.getText());
}); });
}
private static Result<ExtendedClientEvent> toDomainModelExtended(final ConnectionEventJoinRecord record) {
return Result.tryCatch(() -> {
return new ExtendedClientEvent(
record.institution_id,
record.exam_id,
record.exam_user_session_identifer,
record.id,
record.connection_id,
(record.type != null) ? EventType.byId(record.type) : EventType.UNKNOWN,
record.client_time,
record.server_time,
(record.numeric_value != null) ? record.numeric_value.doubleValue() : null,
record.text);
});
} }
} }

View file

@ -29,8 +29,11 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
@ -59,15 +62,18 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
private final UserActivityLogRecordMapper userLogRecordMapper; private final UserActivityLogRecordMapper userLogRecordMapper;
private final UserRecordMapper userRecordMapper; private final UserRecordMapper userRecordMapper;
private final UserService userService; private final UserService userService;
private final JSONMapper jsonMapper;
public UserActivityLogDAOImpl( public UserActivityLogDAOImpl(
final UserActivityLogRecordMapper userLogRecordMapper, final UserActivityLogRecordMapper userLogRecordMapper,
final UserRecordMapper userRecordMapper, final UserRecordMapper userRecordMapper,
final UserService userService) { final UserService userService,
final JSONMapper jsonMapper) {
this.userLogRecordMapper = userLogRecordMapper; this.userLogRecordMapper = userLogRecordMapper;
this.userRecordMapper = userRecordMapper; this.userRecordMapper = userRecordMapper;
this.userService = userService; this.userService = userService;
this.jsonMapper = jsonMapper;
} }
@Override @Override
@ -81,6 +87,18 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
return log(UserLogActivityType.CREATE, entity); return log(UserLogActivityType.CREATE, entity);
} }
@Override
@Transactional
public <E extends Entity> Result<E> logSaveToHistory(final E entity) {
return log(UserLogActivityType.MODIFY, entity, "SEB Exam Configuration : Save To History");
}
@Override
@Transactional
public <E extends Entity> Result<E> logUndo(final E entity) {
return log(UserLogActivityType.MODIFY, entity, "SEB Exam Configuration : Undo");
}
@Override @Override
@Transactional @Transactional
public <E extends Entity> Result<E> logImport(final E entity) { public <E extends Entity> Result<E> logImport(final E entity) {
@ -99,24 +117,6 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
return log(UserLogActivityType.MODIFY, entity); return log(UserLogActivityType.MODIFY, entity);
} }
@Override
@Transactional
public <E extends Entity> Result<E> logActivate(final E entity) {
return log(UserLogActivityType.ACTIVATE, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> logDeactivate(final E entity) {
return log(UserLogActivityType.DEACTIVATE, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> logDelete(final E entity) {
return log(UserLogActivityType.DELETE, entity);
}
@Override @Override
@Transactional @Transactional
public <E extends Entity> Result<E> log( public <E extends Entity> Result<E> log(
@ -130,7 +130,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
@Override @Override
@Transactional @Transactional
public <E extends Entity> Result<E> log(final UserLogActivityType activityType, final E entity) { public <E extends Entity> Result<E> log(final UserLogActivityType activityType, final E entity) {
return log(this.userService.getCurrentUser(), activityType, entity, null); return log(this.userService.getCurrentUser(), activityType, entity, toMessage(entity));
} }
@Override @Override
@ -273,8 +273,8 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
return all( return all(
filterMap.getInstitutionId(), filterMap.getInstitutionId(),
filterMap.getString(UserActivityLog.FILTER_ATTR_USER), filterMap.getString(UserActivityLog.FILTER_ATTR_USER),
filterMap.getUserLogFrom(UserActivityLog.FILTER_ATTR_FROM), filterMap.getUserLogFrom(),
filterMap.getUserLofTo(UserActivityLog.FILTER_ATTR_TO), filterMap.getUserLofTo(),
filterMap.getString(UserActivityLog.FILTER_ATTR_ACTIVITY_TYPES), filterMap.getString(UserActivityLog.FILTER_ATTR_ACTIVITY_TYPES),
filterMap.getString(UserActivityLog.FILTER_ATTR_ENTITY_TYPES), filterMap.getString(UserActivityLog.FILTER_ATTR_ENTITY_TYPES),
predicate); predicate);
@ -312,7 +312,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
SqlBuilder.equalTo(UserActivityLogRecordDynamicSqlSupport.userUuid)) SqlBuilder.equalTo(UserActivityLogRecordDynamicSqlSupport.userUuid))
.where( .where(
UserRecordDynamicSqlSupport.institutionId, UserRecordDynamicSqlSupport.institutionId,
SqlBuilder.isEqualTo(institutionId)) SqlBuilder.isEqualToWhenPresent(institutionId))
.and( .and(
UserActivityLogRecordDynamicSqlSupport.userUuid, UserActivityLogRecordDynamicSqlSupport.userUuid,
SqlBuilder.isEqualToWhenPresent(userId)) SqlBuilder.isEqualToWhenPresent(userId))
@ -480,4 +480,20 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
}); });
} }
private String toMessage(final Entity entity) {
if (entity == null) {
return Constants.EMPTY_NOTE;
}
String entityAsString;
try {
entityAsString = entity.getName() + " = " + this.jsonMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(entity.printSecureCopy());
} catch (final JsonProcessingException e) {
entityAsString = entity.toString();
}
return entityAsString;
}
} }

View file

@ -142,13 +142,13 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy {
.forEach(clientEventMapper::insert); .forEach(clientEventMapper::insert);
return null; return null;
}); });
sqlSessionTemplate.flushStatements();
} else { } else {
sleepTime += 100; sleepTime += 100;
} }
} catch (final Exception e) { } catch (final Exception e) {
log.error("unexpected Error while trying to batch store client-events: ", e); log.error("unexpected Error while trying to batch store client-events: ", e);
} finally {
sqlSessionTemplate.flushStatements();
} }
try { try {

View file

@ -18,7 +18,7 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.Page;
@ -136,15 +136,14 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
private Result<EntityProcessingReport> setActive(final String modelId, final boolean active) { private Result<EntityProcessingReport> setActive(final String modelId, final boolean active) {
final EntityType entityType = this.entityDAO.entityType(); final EntityType entityType = this.entityDAO.entityType();
final BulkAction bulkAction = new BulkAction(
(active) ? BulkActionType.ACTIVATE : BulkActionType.DEACTIVATE,
entityType,
new EntityKey(modelId, entityType));
return this.entityDAO.byModelId(modelId) return this.entityDAO.byModelId(modelId)
.flatMap(this.authorization::checkWrite) .flatMap(this.authorization::checkWrite)
.flatMap(this::validForActivation) .flatMap(this::validForActivation)
.flatMap(entity -> this.bulkActionService.createReport(bulkAction)); .flatMap(entity -> this.bulkActionService.createReport(new BulkAction(
(active) ? BulkActionType.ACTIVATE : BulkActionType.DEACTIVATE,
entityType,
new EntityName(modelId, entityType, entity.getName()))));
} }
protected Result<T> validForActivation(final T entity) { protected Result<T> validForActivation(final T entity) {

View file

@ -0,0 +1,89 @@
/*
* 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 java.util.Collection;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile
@RestController
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONNECTION_ENDPOINT)
public class ClientConnectionController extends ReadonlyEntityController<ClientConnection, ClientConnection> {
protected ClientConnectionController(
final AuthorizationService authorization,
final BulkActionService bulkActionService,
final ClientConnectionDAO clientConnectionDAO,
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
final BeanValidationService beanValidationService) {
super(authorization,
bulkActionService,
clientConnectionDAO,
userActivityLogDAO,
paginationService,
beanValidationService);
}
@Override
public Collection<EntityKey> getDependencies(final String modelId, final BulkActionType bulkActionType) {
throw new UnsupportedOperationException();
}
@Override
protected SqlTable getSQLTableOfEntity() {
return ClientConnectionRecordDynamicSqlSupport.clientConnectionRecord;
}
@Override
protected void checkReadPrivilege(final Long institutionId) {
checkRead(institutionId);
}
@Override
protected Result<ClientConnection> checkReadAccess(final ClientConnection entity) {
return Result.tryCatch(() -> {
checkRead(entity.institutionId);
return entity;
});
}
@Override
protected boolean hasReadAccess(final ClientConnection entity) {
return checkReadAccess(entity).hasValue();
}
private void checkRead(final Long institution) {
this.authorization.checkRole(
institution,
EntityType.CLIENT_EVENT,
UserRole.EXAM_ADMIN,
UserRole.EXAM_SUPPORTER);
}
}

View file

@ -10,40 +10,44 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection; import java.util.Collection;
import javax.validation.Valid;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping; 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 org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; 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.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; 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.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile @WebServiceProfile
@RestController @RestController
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_EVENT_ENDPOINT) @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_EVENT_ENDPOINT)
public class ClientEventController extends EntityController<ClientEvent, ClientEvent> { public class ClientEventController extends ReadonlyEntityController<ClientEvent, ClientEvent> {
private final ClientConnectionDAO clientConnectionDAO; private final ClientConnectionDAO clientConnectionDAO;
private final ClientEventDAO clientEventDAO;
protected ClientEventController( protected ClientEventController(
final AuthorizationService authorization, final AuthorizationService authorization,
@ -62,16 +66,42 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
beanValidationService); beanValidationService);
this.clientConnectionDAO = clientConnectionDAO; this.clientConnectionDAO = clientConnectionDAO;
this.clientEventDAO = entityDAO;
} }
@Override @RequestMapping(
public ClientEvent create(final MultiValueMap<String, String> allRequestParams, final Long institutionId) { path = API.SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT,
throw new UnsupportedOperationException(); method = RequestMethod.GET,
} consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Page<ExtendedClientEvent> getExtendedPage(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@Override // at least current user must have read access for specified entity type within its own institution
public ClientEvent savePut(@Valid final ClientEvent modifyData) { checkReadPrivilege(institutionId);
throw new UnsupportedOperationException();
final FilterMap filterMap = new FilterMap(allRequestParams);
// if current user has no read access for specified entity type within other institution
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance
if (!this.authorization.hasGrant(PrivilegeType.READ, getGrantEntityType())) {
filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
}
return this.paginationService.getPage(
pageNumber,
pageSize,
sort,
getSQLTableOfEntity().name(),
() -> this.clientEventDAO.allMatchingExtended(filterMap, this::hasReadAccess))
.getOrThrow();
} }
@Override @Override
@ -79,11 +109,6 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
protected ClientEvent createNew(final POSTMapper postParams) {
throw new UnsupportedOperationException();
}
@Override @Override
protected SqlTable getSQLTableOfEntity() { protected SqlTable getSQLTableOfEntity() {
return ClientEventRecordDynamicSqlSupport.clientEventRecord; return ClientEventRecordDynamicSqlSupport.clientEventRecord;
@ -107,38 +132,6 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
}); });
} }
@Override
protected void checkModifyPrivilege(final Long institutionId) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.MODIFY,
this.authorization.getUserService().getCurrentUser().uuid());
}
@Override
protected Result<ClientEvent> checkModifyAccess(final ClientEvent entity) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.MODIFY,
this.authorization.getUserService().getCurrentUser().uuid());
}
@Override
protected Result<ClientEvent> checkWriteAccess(final ClientEvent entity) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.WRITE,
this.authorization.getUserService().getCurrentUser().uuid());
}
@Override
protected Result<ClientEvent> checkCreateAccess(final ClientEvent entity) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.WRITE,
this.authorization.getUserService().getCurrentUser().uuid());
}
@Override @Override
protected boolean hasReadAccess(final ClientEvent entity) { protected boolean hasReadAccess(final ClientEvent entity) {
return true; return true;
@ -146,9 +139,10 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
private void checkRead(final Long institution) { private void checkRead(final Long institution) {
this.authorization.checkRole( this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
institution, institution,
EntityType.CLIENT_EVENT); EntityType.CLIENT_EVENT,
UserRole.EXAM_ADMIN,
UserRole.EXAM_SUPPORTER);
} }
} }

View file

@ -12,7 +12,6 @@ import java.util.Collection;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -21,9 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
@ -37,7 +34,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@WebServiceProfile @WebServiceProfile
@RestController @RestController
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.CONFIGURATION_ENDPOINT) @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.CONFIGURATION_ENDPOINT)
public class ConfigurationController extends EntityController<Configuration, Configuration> { public class ConfigurationController extends ReadonlyEntityController<Configuration, Configuration> {
private final ConfigurationDAO configurationDAO; private final ConfigurationDAO configurationDAO;
@ -69,6 +66,7 @@ public class ConfigurationController extends EntityController<Configuration, Con
return this.entityDAO.byModelId(modelId) return this.entityDAO.byModelId(modelId)
.flatMap(this::checkModifyAccess) .flatMap(this::checkModifyAccess)
.flatMap(config -> this.configurationDAO.saveToHistory(config.configurationNodeId)) .flatMap(config -> this.configurationDAO.saveToHistory(config.configurationNodeId))
.flatMap(this.userActivityLogDAO::logSaveToHistory)
.getOrThrow(); .getOrThrow();
} }
@ -82,6 +80,7 @@ public class ConfigurationController extends EntityController<Configuration, Con
return this.entityDAO.byModelId(modelId) return this.entityDAO.byModelId(modelId)
.flatMap(this::checkModifyAccess) .flatMap(this::checkModifyAccess)
.flatMap(config -> this.configurationDAO.undo(config.configurationNodeId)) .flatMap(config -> this.configurationDAO.undo(config.configurationNodeId))
.flatMap(this.userActivityLogDAO::logUndo)
.getOrThrow(); .getOrThrow();
} }
@ -105,21 +104,6 @@ public class ConfigurationController extends EntityController<Configuration, Con
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public Configuration create(final MultiValueMap<String, String> allRequestParams, final Long institutionId) {
throw new UnsupportedOperationException();
}
@Override
public EntityProcessingReport hardDelete(final String modelId) {
throw new UnsupportedOperationException();
}
@Override
protected Configuration createNew(final POSTMapper postParams) {
throw new UnsupportedOperationException();
}
@Override @Override
protected SqlTable getSQLTableOfEntity() { protected SqlTable getSQLTableOfEntity() {
return ConfigurationRecordDynamicSqlSupport.configurationRecord; return ConfigurationRecordDynamicSqlSupport.configurationRecord;

View file

@ -140,7 +140,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
this.entityDAO.byPK(modelId) this.entityDAO.byPK(modelId)
.flatMap(this.authorization::checkRead) .flatMap(this.authorization::checkRead)
.map(this.userActivityLogDAO::logExport); .flatMap(this.userActivityLogDAO::logExport);
final ServletOutputStream outputStream = response.getOutputStream(); final ServletOutputStream outputStream = response.getOutputStream();

View file

@ -69,6 +69,12 @@ public class ConfigurationValueController extends EntityController<Configuration
this.sebExamConfigService = sebExamConfigService; this.sebExamConfigService = sebExamConfigService;
} }
@Override
protected Result<ConfigurationValue> logModify(final ConfigurationValue entity) {
// Skip the modify logging for each individual ConfigurationValue
return Result.of(entity);
}
@Override @Override
protected ConfigurationValue createNew(final POSTMapper postParams) { protected ConfigurationValue createNew(final POSTMapper postParams) {
final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID); final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID);

View file

@ -279,7 +279,7 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
return this.checkCreateAccess(requestModel) return this.checkCreateAccess(requestModel)
.flatMap(this::validForCreate) .flatMap(this::validForCreate)
.flatMap(this.entityDAO::createNew) .flatMap(this.entityDAO::createNew)
.flatMap(this.userActivityLogDAO::logCreate) .flatMap(this::logCreate)
.flatMap(this::notifyCreated) .flatMap(this::notifyCreated)
.getOrThrow(); .getOrThrow();
} }
@ -297,7 +297,7 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
return this.checkModifyAccess(modifyData) return this.checkModifyAccess(modifyData)
.flatMap(this::validForSave) .flatMap(this::validForSave)
.flatMap(this.entityDAO::save) .flatMap(this.entityDAO::save)
.flatMap(this.userActivityLogDAO::logModify) .flatMap(this::logModify)
.flatMap(this::notifySaved) .flatMap(this::notifySaved)
.getOrThrow(); .getOrThrow();
} }
@ -311,15 +311,16 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
method = RequestMethod.DELETE, method = RequestMethod.DELETE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public EntityProcessingReport hardDelete(@PathVariable final String modelId) { public EntityProcessingReport hardDelete(@PathVariable final String modelId) {
final EntityType entityType = this.entityDAO.entityType(); final EntityType entityType = this.entityDAO.entityType();
final BulkAction bulkAction = new BulkAction(
BulkActionType.HARD_DELETE,
entityType,
new EntityKey(modelId, entityType));
return this.entityDAO.byModelId(modelId) return this.entityDAO.byModelId(modelId)
.flatMap(this::checkWriteAccess) .flatMap(this::checkWriteAccess)
.flatMap(entity -> this.bulkActionService.createReport(bulkAction)) .flatMap(entity -> this.bulkActionService.createReport(new BulkAction(
BulkActionType.HARD_DELETE,
entityType,
new EntityName(modelId, entityType, entity.getName()))))
.flatMap(this::logBulkAction)
.getOrThrow(); .getOrThrow();
} }
@ -451,6 +452,33 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
return this.entityDAO.entityType(); return this.entityDAO.entityType();
} }
/** Makes a CREATE user activity log for the specified entity.
* This may be overwritten if the create user activity log should be skipped.
*
* @param entity
* @return Result of entity */
protected Result<T> logCreate(final T entity) {
return this.userActivityLogDAO.logCreate(entity);
}
/** Makes a MODIFY user activity log for the specified entity.
* This may be overwritten if the create user activity log should be skipped.
*
* @param entity
* @return */
protected Result<T> logModify(final T entity) {
return this.userActivityLogDAO.logModify(entity);
}
/** Makes user activity log for a bulk action.
*
* @param bulkActionReport the EntityProcessingReport
* @return Result of entity */
protected Result<EntityProcessingReport> logBulkAction(final EntityProcessingReport bulkActionReport) {
// TODO
return Result.of(bulkActionReport);
}
/** Implements the creation of a new entity from the post parameters given within the POSTMapper /** Implements the creation of a new entity from the post parameters given within the POSTMapper
* *
* @param postParams contains all post parameter from request * @param postParams contains all post parameter from request

View file

@ -100,9 +100,9 @@ public class ExamMonitoringController {
@RequestParam final MultiValueMap<String, String> allRequestParams) { @RequestParam final MultiValueMap<String, String> allRequestParams) {
this.authorization.checkRole( this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
institutionId, institutionId,
EntityType.EXAM); EntityType.EXAM,
UserRole.EXAM_SUPPORTER);
final FilterMap filterMap = new FilterMap(allRequestParams); final FilterMap filterMap = new FilterMap(allRequestParams);
@ -136,9 +136,9 @@ public class ExamMonitoringController {
@PathVariable(name = API.EXAM_API_PARAM_EXAM_ID, required = true) final Long examId) { @PathVariable(name = API.EXAM_API_PARAM_EXAM_ID, required = true) final Long examId) {
this.authorization.checkRole( this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
institutionId, institutionId,
EntityType.EXAM); EntityType.EXAM,
UserRole.EXAM_SUPPORTER);
return this.examSessionService return this.examSessionService
.getConnectionData(examId) .getConnectionData(examId)
@ -159,9 +159,9 @@ public class ExamMonitoringController {
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) { @PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
this.authorization.checkRole( this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
institutionId, institutionId,
EntityType.EXAM); EntityType.EXAM,
UserRole.EXAM_SUPPORTER);
return this.examSessionService return this.examSessionService
.getConnectionData(connectionToken) .getConnectionData(connectionToken)

View file

@ -0,0 +1,102 @@
/*
* 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.validation.Valid;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.util.Result;
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.PermissionDeniedException;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
public abstract class ReadonlyEntityController<T extends Entity, M extends Entity> extends EntityController<T, M> {
private static final String ONLY_READ_ACCESS = "Only read requests available for this entity";
protected ReadonlyEntityController(
final AuthorizationService authorization,
final BulkActionService bulkActionService,
final EntityDAO<T, M> entityDAO,
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
final BeanValidationService beanValidationService) {
super(
authorization,
bulkActionService,
entityDAO,
userActivityLogDAO,
paginationService,
beanValidationService);
}
@Override
public T savePut(@Valid final T modifyData) {
throw new UnsupportedOperationException(ONLY_READ_ACCESS);
}
@Override
public T create(final MultiValueMap<String, String> allRequestParams, final Long institutionId) {
throw new UnsupportedOperationException(ONLY_READ_ACCESS);
}
@Override
public EntityProcessingReport hardDelete(final String modelId) {
throw new UnsupportedOperationException(ONLY_READ_ACCESS);
}
@Override
protected M createNew(final POSTMapper postParams) {
throw new UnsupportedOperationException(ONLY_READ_ACCESS);
}
@Override
protected void checkModifyPrivilege(final Long institutionId) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.MODIFY,
this.authorization.getUserService().getCurrentUser().uuid());
}
@Override
protected Result<T> checkModifyAccess(final T entity) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.MODIFY,
this.authorization.getUserService().getCurrentUser().uuid());
}
@Override
protected Result<T> checkWriteAccess(final T entity) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.WRITE,
this.authorization.getUserService().getCurrentUser().uuid());
}
@Override
protected Result<M> checkCreateAccess(final M entity) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.WRITE,
this.authorization.getUserService().getCurrentUser().uuid());
}
}

View file

@ -8,105 +8,72 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import org.springframework.http.MediaType; import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping; 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 org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; 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.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.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile @WebServiceProfile
@RestController @RestController
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.USER_ACTIVITY_LOG_ENDPOINT) @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.USER_ACTIVITY_LOG_ENDPOINT)
public class UserActivityLogController { public class UserActivityLogController extends ReadonlyEntityController<UserActivityLog, UserActivityLog> {
private final UserActivityLogDAO userActivityLogDAO; protected UserActivityLogController(
private final AuthorizationService authorization;
private final PaginationService paginationService;
public UserActivityLogController(
final UserActivityLogDAO userActivityLogDAO,
final AuthorizationService authorization, final AuthorizationService authorization,
final PaginationService paginationService) { final BulkActionService bulkActionService,
final EntityDAO<UserActivityLog, UserActivityLog> entityDAO,
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
final BeanValidationService beanValidationService) {
this.userActivityLogDAO = userActivityLogDAO; super(
this.authorization = authorization; authorization,
this.paginationService = paginationService; bulkActionService,
entityDAO,
userActivityLogDAO,
paginationService,
beanValidationService);
} }
@InitBinder @Override
public void initBinder(final WebDataBinder binder) throws Exception { protected void checkReadPrivilege(final Long institutionId) {
this.authorization checkRead(institutionId);
.getUserService()
.addUsersInstitutionDefaultPropertySupport(binder);
} }
/** Rest endpoint to get a Page UserActivityLog. @Override
* protected Result<UserActivityLog> checkReadAccess(final UserActivityLog entity) {
* GET /{api}/{entity-type-endpoint-name} return Result.of(entity);
*
* GET /admin-api/v1/useractivity
* GET /admin-api/v1/useractivity?page_number=2&page_size=10&sort=-name
* GET /admin-api/v1/useractivity?name=seb&active=true
*
* @param institutionId The institution identifier of the request.
* Default is the institution identifier of the institution of the current user
* @param pageNumber the number of the page that is requested
* @param pageSize the size of the page that is requested
* @param sort the sort parameter to sort the list of entities before paging
* the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for
* descending sort order
* @param allRequestParams a MultiValueMap of all request parameter that is used for filtering
* @return Page of domain-model-entities of specified type */
@RequestMapping(
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Page<UserActivityLog> getPage(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
// at least current user must have read access for specified entity type within its own institution
checkBaseReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
return this.paginationService.<UserActivityLog> getPage(
pageNumber,
pageSize,
sort,
UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord.name(),
() -> this.userActivityLogDAO.allMatching(filterMap))
.getOrThrow();
} }
private void checkBaseReadPrivilege(final Long institutionId) { @Override
this.authorization.check( protected boolean hasReadAccess(final UserActivityLog entity) {
PrivilegeType.READ, return true;
}
@Override
protected SqlTable getSQLTableOfEntity() {
return UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord;
}
private void checkRead(final Long institution) {
this.authorization.checkRole(
institution,
EntityType.USER_ACTIVITY_LOG, EntityType.USER_ACTIVITY_LOG,
institutionId); UserRole.SEB_SERVER_ADMIN,
UserRole.INSTITUTIONAL_ADMIN);
} }
} }

View file

@ -25,6 +25,7 @@
<Logger name="ch.ethz.seb.sebserver.gui" level="INFO" additivity="true" /> <Logger name="ch.ethz.seb.sebserver.gui" level="INFO" additivity="true" />
<Logger name="ch.ethz.seb.sebserver.webservice" level="INFO" additivity="true" /> <Logger name="ch.ethz.seb.sebserver.webservice" level="INFO" additivity="true" />
<Logger name="org.apache.ibatis.datasource" level="INFO" additivity="true" /> <Logger name="org.apache.ibatis.datasource" level="INFO" additivity="true" />
<Logger name="org.mybatis" level="DEBUG" additivity="true" />
<Logger name="org.mybatis.generator" level="INFO" additivity="true" /> <Logger name="org.mybatis.generator" level="INFO" additivity="true" />
<Logger name="org.springframework.boot" level="INFO" additivity="true" /> <Logger name="org.springframework.boot" level="INFO" additivity="true" />
<Logger name="org.springframework.security" level="INFO" additivity="true" /> <Logger name="org.springframework.security" level="INFO" additivity="true" />
@ -32,7 +33,7 @@
<Logger name="org.springframework.web" level="INFO" additivity="true" /> <Logger name="org.springframework.web" level="INFO" additivity="true" />
<Logger name="org.springframework.security.oauth2" level="INFO" additivity="true" /> <Logger name="org.springframework.security.oauth2" level="INFO" additivity="true" />
<Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig" level="DEBUG" additivity="true" /> <Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig" level="INFO" additivity="true" />
<Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebExamConfigServiceImpl" level="TRACE" additivity="true" /> <Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebExamConfigServiceImpl" level="TRACE" additivity="true" />
</springProfile> </springProfile>

View file

@ -941,6 +941,14 @@ sebserver.monitoring.exam.connection.event.type.WARN_LOG=Warn
sebserver.monitoring.exam.connection.event.type.ERROR_LOG=Error sebserver.monitoring.exam.connection.event.type.ERROR_LOG=Error
sebserver.monitoring.exam.connection.event.type.LAST_PING=Last Ping sebserver.monitoring.exam.connection.event.type.LAST_PING=Last Ping
sebserver.monitoring.exam.connection.status.UNDEFINED=Undefined
sebserver.monitoring.exam.connection.status.CONNECTION_REQUESTED=Connection Requested,
sebserver.monitoring.exam.connection.status.AUTHENTICATED=Authenticated,
sebserver.monitoring.exam.connection.status.ESTABLISHED=Established,
sebserver.monitoring.exam.connection.status.CLOSED=Closed,
sebserver.monitoring.exam.connection.status.ABORTED=Aborted,
sebserver.monitoring.exam.connection.status.RELEASED=Released
################################ ################################
# Logs # Logs
################################ ################################
@ -956,7 +964,8 @@ sebserver.userlogs.list.column.institution=Institution
sebserver.userlogs.list.column.user=User sebserver.userlogs.list.column.user=User
sebserver.userlogs.list.column.dateTime=Date sebserver.userlogs.list.column.dateTime=Date
sebserver.userlogs.list.column.activityType=User Activity sebserver.userlogs.list.column.activityType=User Activity
sebserver.userlogs.list.column.entityType=Entity sebserver.userlogs.list.column.entityType=Entity Type
sebserver.userlogs.list.column.entityId=Entity-ID
sebserver.userlogs.list.column.message=Message sebserver.userlogs.list.column.message=Message
sebserver.userlogs.details.title=User Activity Log Details sebserver.userlogs.details.title=User Activity Log Details
@ -969,3 +978,32 @@ sebserver.seblogs.list.title=SEB Client Logs
sebserver.seblogs.list.actions=Selected Log sebserver.seblogs.list.actions=Selected Log
sebserver.seblogs.list.empty=No SEB client logs has been found. Please adapt or clear the filter sebserver.seblogs.list.empty=No SEB client logs has been found. Please adapt or clear the filter
sebserver.seblogs.list.column.exam=Exam
sebserver.seblogs.list.column.client-session=User Session-ID
sebserver.seblogs.list.column.type=Event Type
sebserver.seblogs.list.column.time=Event Time
sebserver.seblogs.list.column.value=Value
sebserver.seblogs.details.title=SEB Client Log Details
sebserver.seblogs.details.event.title=Event
sebserver.seblogs.details.connection.title=SEB Connection Details
sebserver.seblogs.details.exam.title=Exam Details
sebserver.seblogs.form.column.client-session=Session-ID
sebserver.seblogs.form.column.type=Event Type
sebserver.seblogs.form.column.server-time=Server Time
sebserver.seblogs.form.column.client-time=SEB Client Time
sebserver.seblogs.form.column.value=Value
sebserver.seblogs.form.column.message=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.token=SEB Connection Token
sebserver.seblogs.form.column.connection.status=Connection Status
sebserver.seblogs.form.column.exam.name=Name
sebserver.seblogs.form.column.exam.description=Description
sebserver.seblogs.form.column.exam.type=Type
sebserver.seblogs.form.column.exam.startTime=Start Time
sebserver.seblogs.form.column.exam.endTime=End Time

View file

@ -663,7 +663,6 @@ TabItem:selected:hover:first {
} }
Widget-ToolTip { Widget-ToolTip {
padding: 1px 3px 2px 3px; padding: 1px 3px 2px 3px;
background-color: #82be1e; background-color: #82be1e;

View file

@ -58,7 +58,7 @@ public class InstitutionTest {
json = jsonMapper.writeValueAsString(namesList); json = jsonMapper.writeValueAsString(namesList);
assertEquals( assertEquals(
"[{\"entityType\":\"INSTITUTION\",\"modelId\":\"1\",\"name\":\"InstOne\"},{\"entityType\":\"INSTITUTION\",\"modelId\":\"2\",\"name\":\"InstTwo\"},{\"entityType\":\"INSTITUTION\",\"modelId\":\"3\",\"name\":\"InstThree\"}]", "[{\"modelId\":\"1\",\"entityType\":\"INSTITUTION\",\"name\":\"InstOne\"},{\"modelId\":\"2\",\"entityType\":\"INSTITUTION\",\"name\":\"InstTwo\"},{\"modelId\":\"3\",\"entityType\":\"INSTITUTION\",\"name\":\"InstThree\"}]",
json); json);
} }

View file

@ -41,12 +41,12 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
}); });
assertNotNull(logs); assertNotNull(logs);
assertTrue(2 == logs.content.size()); assertTrue(5 == logs.content.size());
} }
@Test @Test
public void getAllAsSEBAdminForUser() throws Exception { public void getAllAsInstAdmin2ForUser() throws Exception {
final String token = getSebAdminAccess(); final String token = getAdminInstitution2Access();
// for a user in another institution, the institution has to be defined // for a user in another institution, the institution has to be defined
Page<UserActivityLog> logs = this.jsonMapper.readValue( Page<UserActivityLog> logs = this.jsonMapper.readValue(
this.mockMvc this.mockMvc
@ -63,7 +63,7 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
// for a user in the same institution no institution is needed // for a user in the same institution no institution is needed
logs = this.jsonMapper.readValue( logs = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?user=user2") this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?user=user3")
.header("Authorization", "Bearer " + token) .header("Authorization", "Bearer " + token)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -76,7 +76,7 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
} }
@Test @Test
public void getAllAsSEBAdminInTimeRange() throws Exception { public void getAllAsInst2AdminInTimeRange() throws Exception {
final DateTime zeroDate = DateTime.parse("1970-01-01T00:00:00Z", Constants.STANDARD_DATE_TIME_FORMATTER); final DateTime zeroDate = DateTime.parse("1970-01-01T00:00:00Z", Constants.STANDARD_DATE_TIME_FORMATTER);
assertEquals("0", String.valueOf(zeroDate.getMillis())); assertEquals("0", String.valueOf(zeroDate.getMillis()));
final String sec2 = zeroDate.plus(1000).toString(Constants.STANDARD_DATE_TIME_FORMATTER); final String sec2 = zeroDate.plus(1000).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
@ -84,7 +84,7 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
final String sec5 = zeroDate.plus(5000).toString(Constants.STANDARD_DATE_TIME_FORMATTER); final String sec5 = zeroDate.plus(5000).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
final String sec6 = zeroDate.plus(6000).toString(Constants.STANDARD_DATE_TIME_FORMATTER); final String sec6 = zeroDate.plus(6000).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
final String token = getSebAdminAccess(); final String token = getAdminInstitution2Access();
Page<UserActivityLog> logs = this.jsonMapper.readValue( Page<UserActivityLog> logs = this.jsonMapper.readValue(
this.mockMvc.perform( this.mockMvc.perform(
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?institutionId=2&from=" + sec2) get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?institutionId=2&from=" + sec2)
@ -149,9 +149,23 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
public void getAllAsSEBAdminForActivityType() throws Exception { public void getAllAsSEBAdminForActivityType() throws Exception {
final String token = getSebAdminAccess(); final String token = getSebAdminAccess();
Page<UserActivityLog> logs = this.jsonMapper.readValue( Page<UserActivityLog> logs = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?activity_types=CREATE") this.mockMvc.perform(
.header("Authorization", "Bearer " + token) get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?activity_types=CREATE")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)) .header("Authorization", "Bearer " + token)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<Page<UserActivityLog>>() {
});
assertNotNull(logs);
assertTrue(3 == logs.content.size());
logs = this.jsonMapper.readValue(
this.mockMvc.perform(
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?institutionId=1&activity_types=CREATE")
.header("Authorization", "Bearer " + token)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(), .andReturn().getResponse().getContentAsString(),
new TypeReference<Page<UserActivityLog>>() { new TypeReference<Page<UserActivityLog>>() {
@ -164,7 +178,7 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
this.mockMvc this.mockMvc
.perform( .perform(
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
+ "?activity_types=CREATE,MODIFY") + "?institutionId=1&activity_types=CREATE,MODIFY")
.header("Authorization", "Bearer " + token) .header("Authorization", "Bearer " + token)
.header(HttpHeaders.CONTENT_TYPE, .header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE)) MediaType.APPLICATION_FORM_URLENCODED_VALUE))
@ -177,12 +191,13 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
assertTrue(2 == logs.content.size()); assertTrue(2 == logs.content.size());
// for other institution (2) // for other institution (2)
final String adminInstitution2Access = getAdminInstitution2Access();
logs = this.jsonMapper.readValue( logs = this.jsonMapper.readValue(
this.mockMvc this.mockMvc
.perform( .perform(
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
+ "?institutionId=2&activity_types=CREATE,MODIFY") + "?institutionId=2&activity_types=CREATE,MODIFY")
.header("Authorization", "Bearer " + token) .header("Authorization", "Bearer " + adminInstitution2Access)
.header(HttpHeaders.CONTENT_TYPE, .header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE)) MediaType.APPLICATION_FORM_URLENCODED_VALUE))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -199,9 +214,10 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
final String token = getSebAdminAccess(); final String token = getSebAdminAccess();
Page<UserActivityLog> logs = this.jsonMapper.readValue( Page<UserActivityLog> logs = this.jsonMapper.readValue(
this.mockMvc this.mockMvc
.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?entity_types=INSTITUTION") .perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
.header("Authorization", "Bearer " + token) + "?institutionId=1&entity_types=INSTITUTION")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)) .header("Authorization", "Bearer " + token)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(), .andReturn().getResponse().getContentAsString(),
new TypeReference<Page<UserActivityLog>>() { new TypeReference<Page<UserActivityLog>>() {
@ -214,7 +230,7 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
this.mockMvc this.mockMvc
.perform( .perform(
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
+ "?entity_types=INSTITUTION,EXAM") + "?institutionId=1&entity_types=INSTITUTION,EXAM")
.header("Authorization", "Bearer " + token) .header("Authorization", "Bearer " + token)
.header(HttpHeaders.CONTENT_TYPE, .header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE)) MediaType.APPLICATION_FORM_URLENCODED_VALUE))
@ -226,12 +242,13 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
assertNotNull(logs); assertNotNull(logs);
assertTrue(2 == logs.content.size()); assertTrue(2 == logs.content.size());
final String adminInstitution2Access = getAdminInstitution2Access();
logs = this.jsonMapper.readValue( logs = this.jsonMapper.readValue(
this.mockMvc this.mockMvc
.perform( .perform(
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
+ "?entity_types=INSTITUTION,EXAM&institutionId=2") + "?entity_types=INSTITUTION,EXAM&institutionId=2")
.header("Authorization", "Bearer " + token) .header("Authorization", "Bearer " + adminInstitution2Access)
.header(HttpHeaders.CONTENT_TYPE, .header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE)) MediaType.APPLICATION_FORM_URLENCODED_VALUE))
.andExpect(status().isOk()) .andExpect(status().isOk())