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 =
"/{" + 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_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 */
default EntityName toName() {
return new EntityName(
this.entityType(),
this.getModelId(),
this.entityType(),
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 com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
/** 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. */
@JsonIgnoreProperties(ignoreUnknown = true)
public class EntityKey implements ModelIdAware, Serializable {
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;
@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)
public final String name;
@JsonCreator
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_ENTITY_TYPE, required = true) final EntityType entityType,
@JsonProperty(value = "name", required = true) final String name) {
this.entityType = entityType;
this.modelId = id;
super(id, entityType);
this.name = name;
}
public EntityName(final EntityKey entityKey, final String name) {
this.entityType = entityKey.entityType;
this.modelId = entityKey.modelId;
super(entityKey.modelId, entityKey.entityType);
this.name = name;
}
@Override
public EntityType getEntityType() {
return this.entityType;
}
@ -63,40 +60,6 @@ public class EntityName implements ModelIdAware {
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
public String toString() {
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.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Activatable;
@ -30,6 +31,23 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
@JsonIgnoreProperties(ignoreUnknown = true)
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
/** 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 */

View file

@ -15,11 +15,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
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.POSTMapper;
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.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.sebconfig.ConfigurationNode.ConfigurationStatus;
@ -143,10 +145,12 @@ public final class ExamConfigurationMap implements GrantEntity {
return this.userNames;
}
@JsonIgnore
public CharSequence getEncryptSecret() {
return this.encryptSecret;
}
@JsonIgnore
public CharSequence getConfirmEncryptSecret() {
return this.confirmEncryptSecret;
}
@ -168,6 +172,21 @@ public final class ExamConfigurationMap implements GrantEntity {
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
public String toString() {
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.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
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.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.GrantEntity;
@ -161,6 +163,20 @@ public final class LmsSetup implements GrantEntity, Activatable {
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
public String toString() {
final StringBuilder builder = new StringBuilder();
@ -174,8 +190,6 @@ public final class LmsSetup implements GrantEntity, Activatable {
builder.append(this.lmsType);
builder.append(", lmsAuthName=");
builder.append(this.lmsAuthName);
builder.append(", lmsAuthSecret=");
builder.append(this.lmsAuthSecret);
builder.append(", lmsApiUrl=");
builder.append(this.lmsApiUrl);
builder.append(", lmsRestApiToken=");
@ -188,8 +202,8 @@ public final class LmsSetup implements GrantEntity, Activatable {
public static EntityName toName(final LmsSetup lmsSetup) {
return new EntityName(
EntityType.LMS_SETUP,
String.valueOf(lmsSetup.id),
EntityType.LMS_SETUP,
lmsSetup.name);
}

View file

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

View file

@ -19,9 +19,6 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
@JsonIgnoreProperties(ignoreUnknown = true)
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 {
UNDEFINED,
CONNECTION_REQUESTED,
@ -32,6 +29,20 @@ public final class ClientConnection implements GrantEntity {
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)
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;
@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 */
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.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@ -85,4 +86,13 @@ public class PasswordChange implements Entity {
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);
return headers;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -157,7 +157,7 @@ public class QuizDiscoveryList implements TemplateComposer {
.withExec(action -> this.showDetails(action, t.getSelectedROWData()))
.noEventPropagation()
.create())
.compose(content);
.compose(pageContext.copyOf(content));
// propagate content actions to action-pane
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) {
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)
.readonly(true)
.addField(FormBuilder.singleSelection(
@ -251,7 +255,6 @@ public class QuizDiscoveryList implements TemplateComposer {
QUIZ_DETAILS_URL_TEXT_KEY,
quizData.startURL))
.build();
this.widgetFactory.labelSeparator(pc.getParent());
}
}

View file

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

View file

@ -8,26 +8,374 @@
package ch.ethz.seb.sebserver.gui.content;
import java.util.Map;
import java.util.function.Function;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnection;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy
@Component
@GuiProfile
public class SebClientLogs implements TemplateComposer {
public SebClientLogs() {
// TODO Auto-generated constructor stub
private static final Logger log = LoggerFactory.getLogger(SebClientLogs.class);
private static final LocTextKey DETAILS_TITLE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.details.title");
private static final LocTextKey TITLE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.list.title");
private static final LocTextKey EMPTY_TEXT_KEY =
new LocTextKey("sebserver.seblogs.list.empty");
private static final LocTextKey EXAM_TEXT_KEY =
new LocTextKey("sebserver.seblogs.list.column.exam");
private static final LocTextKey CLIENT_SESSION_TEXT_KEY =
new LocTextKey("sebserver.seblogs.list.column.client-session");
private static final LocTextKey TYPE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.list.column.type");
private static final LocTextKey TIME_TEXT_KEY =
new LocTextKey("sebserver.seblogs.list.column.time");
private static final LocTextKey VALUE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.list.column.value");
private static final LocTextKey DETAILS_EVENT_TILE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.details.event.title");
private static final LocTextKey DETAILS_CONNECTION_TILE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.details.connection.title");
private static final LocTextKey DETAILS_EXAM_TILE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.details.exam.title");
private static final LocTextKey FORM_TYPE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.type");
private static final LocTextKey FORM_SERVERTIME_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.server-time");
private static final LocTextKey FORM_CLIENTTIME_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.client-time");
private static final LocTextKey FORM_VALUE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.value");
private static final LocTextKey FORM_MESSAGE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.message");
private static final LocTextKey FORM_SESSION_ID_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.connection.session-id");
private static final LocTextKey FORM_ADDRESS_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.connection.address");
private static final LocTextKey FORM_TOKEN_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.connection.token");
private static final LocTextKey FORM_STATUS_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.connection.status");
private static final LocTextKey FORM_EXAM_NAME_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.exam.name");
private static final LocTextKey FORM_DESC_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.exam.description");
private static final LocTextKey FORM_EXAM_TYPE_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.exam.type");
private static final LocTextKey FORM_START_TIME_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.exam.startTime");
private static final LocTextKey FORM_END_TIME_TEXT_KEY =
new LocTextKey("sebserver.seblogs.form.column.exam.endTime");
private final static LocTextKey EMPTY_SELECTION_TEXT =
new LocTextKey("sebserver.seblogs.info.pleaseSelect");
private final TableFilterAttribute examFilter;
private final TableFilterAttribute clientSessionFilter;
private final TableFilterAttribute eventTypeFilter;
private final PageService pageService;
private final ResourceService resourceService;
private final RestService restService;
private final I18nSupport i18nSupport;
private final WidgetFactory widgetFactory;
private final int pageSize;
public SebClientLogs(
final PageService pageService,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.restService = this.resourceService.getRestService();
this.i18nSupport = this.resourceService.getI18nSupport();
this.widgetFactory = pageService.getWidgetFactory();
this.pageSize = pageSize;
this.examFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
ExtendedClientEvent.FILTER_ATTRIBUTE_EXAM,
this.resourceService::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
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
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST)
.create())
.compose(content);
.compose(pageContext.copyOf(content));
final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE);
pageActionBuilder

View file

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

View file

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

View file

@ -200,6 +200,11 @@ public class FormBuilder {
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(
final String name,
final LocTextKey label,

View file

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

View file

@ -17,6 +17,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
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.ConfigurationStatus;
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.user.UserActivityLog;
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.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.GetExamNames;
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.useraccount.GetUserAccountNames;
@ -75,6 +79,9 @@ public class ResourceService {
EntityType.USER_ROLE,
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 EXAM_TYPE_PREFIX = "sebserver.exam.type.";
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 USER_ACTIVITY_TYPE_PREFIX = "sebserver.overall.types.activityType.";
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 INACTIVE_TEXT_KEY = new LocTextKey("sebserver.overall.status.inactive");
@ -138,6 +146,7 @@ public class ResourceService {
public List<Tuple<String>> clientEventTypeResources() {
return Arrays.asList(EventType.values())
.stream()
.filter(Predicate.not(CLIENT_EVENT_TYPE_EXCLUDE_MAP::contains))
.map(eventType -> new Tuple<>(
eventType.name(),
getEventTypeName(eventType)))
@ -145,6 +154,13 @@ public class ResourceService {
.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) {
if (eventType == null) {
return Constants.EMPTY_NOTE;
@ -384,6 +400,51 @@ public class ResourceService {
.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() {
return this.restService.getBuilder(GetExamConfigMappingNames.class)
.withQueryParam(
@ -398,13 +459,4 @@ public class ResourceService {
.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 ch.ethz.seb.sebserver.gbl.util.Utils;
public interface I18nSupport {
/** Get all supported languages as a collection of Locale
@ -38,6 +40,18 @@ public interface I18nSupport {
* @return date formatted date String to display */
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.
* This uses the date-format defined by either the attribute 'sebserver.gui.datetime.displayformat'
* or the Constants.DEFAULT_DISPLAY_DATE_TIME_FORMAT
@ -48,6 +62,18 @@ public interface I18nSupport {
* @return date formatted date time String to display */
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.
* This uses the date-format defined by either the attribute 'sebserver.gui.time.displayformat'
* or the Constants.DEFAULT_DISPLAY_TIME_FORMAT
@ -58,6 +84,18 @@ public interface I18nSupport {
* @return date formatted time String to display */
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
* 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 rows the number of rows of the from
* @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.
*
* @param apiCall the SEB Server API RestCall that feeds the table with data
* @param <T> the type of the Entity of the table
* @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.
*

View file

@ -13,7 +13,6 @@ import java.util.function.Supplier;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
@ -42,6 +41,7 @@ public class ModalInputDialog<T> extends Dialog {
private final WidgetFactory widgetFactory;
private int dialogWidth = 400;
private int dialogHeight = 600;
private final int buttonWidth = 100;
public ModalInputDialog(
final Shell parent,
@ -75,25 +75,21 @@ public class ModalInputDialog<T> extends Dialog {
shell.setText(this.widgetFactory.getI18nSupport().getText(title));
shell.setLayout(new GridLayout(2, true));
final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, false, false);
gridData2.widthHint = this.dialogWidth;
//gridData2.heightHint = this.dialogHeight;
shell.setLayoutData(gridData2);
final Composite main = new Composite(shell, SWT.NONE);
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.widthHint = this.dialogWidth;
main.setLayoutData(gridData);
final Supplier<T> valueSuppier = contentComposer.compose(main);
final Point computeSize = main.computeSize(SWT.DEFAULT, SWT.DEFAULT);
gridData.heightHint = (computeSize.y < this.dialogHeight) ? computeSize.y : this.dialogHeight;
gridData.heightHint = calcDialogHeight(main);
final Button ok = this.widgetFactory.buttonLocalized(shell, OK_TEXT_KEY);
GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
data.widthHint = 100;
data.widthHint = this.buttonWidth;
ok.setLayoutData(data);
ok.addListener(SWT.Selection, event -> {
if (valueSuppier != null) {
@ -109,7 +105,7 @@ public class ModalInputDialog<T> extends Dialog {
final Button cancel = this.widgetFactory.buttonLocalized(shell, CANCEL_TEXT_KEY);
data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
data.widthHint = 100;
data.widthHint = this.buttonWidth;
cancel.setLayoutData(data);
cancel.addListener(SWT.Selection, event -> {
if (cancelCallback != null) {
@ -132,18 +128,22 @@ public class ModalInputDialog<T> extends Dialog {
shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key);
shell.setText(this.widgetFactory.getI18nSupport().getText(title));
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);
main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
gridData.widthHint = this.dialogWidth;
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 GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
data.widthHint = 100;
data.widthHint = this.buttonWidth;
close.setLayoutData(data);
close.addListener(SWT.Selection, event -> {
shell.close();
@ -164,4 +164,16 @@ public class ModalInputDialog<T> extends Dialog {
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,
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/.
*/
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.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,
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 final static String STATUS_LOC_TEXT_KEY_PREFIX = "sebserver.monitoring.connection.status.";
private final static LocTextKey EXAM_NAME_TEXT_KEY =
new LocTextKey("sebserver.monitoring.connection.list.column.examname");
private final static LocTextKey CONNECTION_ID_TEXT_KEY =
@ -163,14 +161,10 @@ public class ClientConnectionDetails {
}
String getStatusName() {
String name;
if (this.connectionData != null && this.connectionData.clientConnection.status != null) {
name = this.connectionData.clientConnection.status.name();
} else {
name = ConnectionStatus.UNDEFINED.name();
}
return this.pageService.getI18nSupport()
.getText(STATUS_LOC_TEXT_KEY_PREFIX + name, name);
return this.pageService.getResourceService().localizedClientConnectionStatusName(
(this.connectionData != null && this.connectionData.clientConnection != null)
? this.connectionData.clientConnection.status
: ConnectionStatus.UNDEFINED);
}
}

View file

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

View file

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

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
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.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.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.widget.Selection;
@ -302,11 +304,17 @@ public class TableFilter<ROW extends Entity> {
final Composite innerComposite = createInnerComposite(parent);
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
.selectionLocalized(
ch.ethz.seb.sebserver.gui.widget.Selection.Type.SINGLE,
innerComposite,
this.attribute.resourceSupplier);
resourceSupplier);
this.selector
.adaptToControl()
.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.LocTextKey;
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;
@Lazy
@ -196,6 +197,23 @@ public class WidgetFactory {
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) {
final Button button = new Button(parent, SWT.NONE);
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));

View file

@ -8,8 +8,16 @@
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.ResultType;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.SqlBuilder;
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.util.SqlProviderAdapter;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
@Mapper
@ -36,4 +45,86 @@ public interface ClientEventExtentionMapper {
.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(final String sort, final SqlTable table);
void setDefaultLimit(final String sort, final String tableName);
int getPageNumber(final Integer pageNumber);

View file

@ -94,8 +94,8 @@ public class PaginationServiceImpl implements PaginationService {
}
@Override
public void setDefaultLimit(final String sort, final SqlTable table) {
setPagination(1, this.maxPageSize, sort, table);
public void setDefaultLimit(final String sort, final String tableName) {
setPagination(1, this.maxPageSize, sort, tableName);
}
@Override
@ -131,9 +131,9 @@ public class PaginationServiceImpl implements PaginationService {
final Supplier<Result<Collection<T>>> delegate) {
return Result.tryCatch(() -> {
final SqlTable table = SqlTable.of(tableName);
//final SqlTable table = SqlTable.of(tableName);
final com.github.pagehelper.Page<Object> page =
setPagination(pageNumber, pageSize, sort, table);
setPagination(pageNumber, pageSize, sort, tableName);
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)) {
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) {
final String sortColumn = PageSortOrder.decode(sort);
if (StringUtils.isBlank(sortColumn)) {
return this.defaultSortColumn.get(table.name());
return this.defaultSortColumn.get(columnName);
}
return mapping.get(sortColumn);
}
return this.defaultSortColumn.get(table.name());
return this.defaultSortColumn.get(columnName);
}
private com.github.pagehelper.Page<Object> setPagination(
final Integer pageNumber,
final Integer pageSize,
final String sort,
final SqlTable table) {
final String sortMappingName) {
final com.github.pagehelper.Page<Object> startPage =
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 String sortColumnName = verifySortColumnName(sort, table);
final String sortColumnName = verifySortColumnName(sort, sortMappingName);
if (StringUtils.isNotBlank(sortColumnName)) {
switch (sortOrder) {
case DESCENDING: {

View file

@ -8,7 +8,10 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
@ -233,17 +236,21 @@ public interface AuthorizationService {
/** Checks if the current user has a specified role.
* 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 type EntityType for PermissionDeniedException
* @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
.getUserService()
.getCurrentUser();
final List<UserRole> rolesList = (roles == null)
? Collections.emptyList()
: Arrays.asList(roles);
if (!currentUser.institutionId().equals(institution) ||
!currentUser.getUserRoles().contains(role)) {
Collections.disjoint(currentUser.getUserRoles(), rolesList)) {
throw new PermissionDeniedException(
type,

View file

@ -19,7 +19,10 @@ import java.util.stream.Collectors;
import org.springframework.context.ApplicationEventPublisher;
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.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
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 UserActivityLogDAO userActivityLogDAO;
private final ApplicationEventPublisher applicationEventPublisher;
private final JSONMapper jsonMapper;
public BulkActionServiceImpl(
final Collection<BulkActionSupportDAO<?>> supporter,
final UserActivityLogDAO userActivityLogDAO,
final ApplicationEventPublisher applicationEventPublisher) {
final ApplicationEventPublisher applicationEventPublisher,
final JSONMapper jsonMapper) {
this.supporter = new HashMap<>();
for (final BulkActionSupportDAO<?> support : supporter) {
@ -46,6 +51,7 @@ public class BulkActionServiceImpl implements BulkActionService {
}
this.userActivityLogDAO = userActivityLogDAO;
this.applicationEventPublisher = applicationEventPublisher;
this.jsonMapper = jsonMapper;
}
@Override
@ -123,11 +129,12 @@ public class BulkActionServiceImpl implements BulkActionService {
}
for (final EntityKey key : action.dependencies) {
this.userActivityLogDAO.log(
activityType,
key.entityType,
key.modelId,
"bulk action dependency");
"Bulk Action - Dependency : " + toLogMessage(key));
}
for (final EntityKey key : action.sources) {
@ -135,10 +142,22 @@ public class BulkActionServiceImpl implements BulkActionService {
activityType,
key.entityType,
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) {
switch (action.type) {
case ACTIVATE:

View file

@ -8,8 +8,17 @@
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.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.util.Result;
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()
.stream()
.map(entity -> new EntityName(
entity.entityType(),
entity.getModelId(),
entity.entityType(),
entity.getName()))
.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.ClientEvent;
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.UserInfo;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -235,14 +236,14 @@ public class FilterMap extends POSTMapper {
false);
}
public Long getUserLogFrom(final String filterAttrFrom) {
public Long getUserLogFrom() {
return getFromToValue(
UserActivityLog.FILTER_ATTR_FROM_TO,
UserActivityLog.FILTER_ATTR_FROM,
true);
}
public Long getUserLofTo(final String filterAttrTo) {
public Long getUserLofTo() {
return getFromToValue(
UserActivityLog.FILTER_ATTR_FROM_TO,
UserActivityLog.FILTER_ATTR_TO,
@ -253,6 +254,10 @@ public class FilterMap extends POSTMapper {
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) {
final Long value = getFromToValue(nameCombi, from);
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 */
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
*
* @param entity the Entity
@ -47,24 +51,6 @@ public interface UserActivityLogDAO extends
* @return Result of the Entity or referring to an Error id happened */
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.
*
* @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.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.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
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.ClientEventRecordMapper;
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 {
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.clientEventExtentionMapper = clientEventExtentionMapper;
}
@Override
@ -100,7 +110,54 @@ public class ClientEventDAOImpl implements ClientEventDAO {
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate)
.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
@ -183,7 +240,23 @@ public class ClientEventDAOImpl implements ClientEventDAO {
(numericValue != null) ? numericValue.doubleValue() : null,
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.util.CollectionUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.ethz.seb.sebserver.gbl.Constants;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
@ -59,15 +62,18 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
private final UserActivityLogRecordMapper userLogRecordMapper;
private final UserRecordMapper userRecordMapper;
private final UserService userService;
private final JSONMapper jsonMapper;
public UserActivityLogDAOImpl(
final UserActivityLogRecordMapper userLogRecordMapper,
final UserRecordMapper userRecordMapper,
final UserService userService) {
final UserService userService,
final JSONMapper jsonMapper) {
this.userLogRecordMapper = userLogRecordMapper;
this.userRecordMapper = userRecordMapper;
this.userService = userService;
this.jsonMapper = jsonMapper;
}
@Override
@ -81,6 +87,18 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
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
@Transactional
public <E extends Entity> Result<E> logImport(final E entity) {
@ -99,24 +117,6 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
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
@Transactional
public <E extends Entity> Result<E> log(
@ -130,7 +130,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
@Override
@Transactional
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
@ -273,8 +273,8 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
return all(
filterMap.getInstitutionId(),
filterMap.getString(UserActivityLog.FILTER_ATTR_USER),
filterMap.getUserLogFrom(UserActivityLog.FILTER_ATTR_FROM),
filterMap.getUserLofTo(UserActivityLog.FILTER_ATTR_TO),
filterMap.getUserLogFrom(),
filterMap.getUserLofTo(),
filterMap.getString(UserActivityLog.FILTER_ATTR_ACTIVITY_TYPES),
filterMap.getString(UserActivityLog.FILTER_ATTR_ENTITY_TYPES),
predicate);
@ -312,7 +312,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
SqlBuilder.equalTo(UserActivityLogRecordDynamicSqlSupport.userUuid))
.where(
UserRecordDynamicSqlSupport.institutionId,
SqlBuilder.isEqualTo(institutionId))
SqlBuilder.isEqualToWhenPresent(institutionId))
.and(
UserActivityLogRecordDynamicSqlSupport.userUuid,
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);
return null;
});
sqlSessionTemplate.flushStatements();
} else {
sleepTime += 100;
}
} catch (final Exception e) {
log.error("unexpected Error while trying to batch store client-events: ", e);
} finally {
sqlSessionTemplate.flushStatements();
}
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.EntityType;
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.GrantEntity;
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) {
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)
.flatMap(this.authorization::checkWrite)
.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) {

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 javax.validation.Valid;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
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.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.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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
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.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.dao.ClientConnectionDAO;
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.validation.BeanValidationService;
@WebServiceProfile
@RestController
@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 ClientEventDAO clientEventDAO;
protected ClientEventController(
final AuthorizationService authorization,
@ -62,16 +66,42 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
beanValidationService);
this.clientConnectionDAO = clientConnectionDAO;
this.clientEventDAO = entityDAO;
}
@Override
public ClientEvent create(final MultiValueMap<String, String> allRequestParams, final Long institutionId) {
throw new UnsupportedOperationException();
}
@RequestMapping(
path = API.SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT,
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
public ClientEvent savePut(@Valid final ClientEvent modifyData) {
throw new UnsupportedOperationException();
// at least current user must have read access for specified entity type within its own institution
checkReadPrivilege(institutionId);
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
@ -79,11 +109,6 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
throw new UnsupportedOperationException();
}
@Override
protected ClientEvent createNew(final POSTMapper postParams) {
throw new UnsupportedOperationException();
}
@Override
protected SqlTable getSQLTableOfEntity() {
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
protected boolean hasReadAccess(final ClientEvent entity) {
return true;
@ -146,9 +139,10 @@ public class ClientEventController extends EntityController<ClientEvent, ClientE
private void checkRead(final Long institution) {
this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
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.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
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.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
@ -37,7 +34,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@WebServiceProfile
@RestController
@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;
@ -69,6 +66,7 @@ public class ConfigurationController extends EntityController<Configuration, Con
return this.entityDAO.byModelId(modelId)
.flatMap(this::checkModifyAccess)
.flatMap(config -> this.configurationDAO.saveToHistory(config.configurationNodeId))
.flatMap(this.userActivityLogDAO::logSaveToHistory)
.getOrThrow();
}
@ -82,6 +80,7 @@ public class ConfigurationController extends EntityController<Configuration, Con
return this.entityDAO.byModelId(modelId)
.flatMap(this::checkModifyAccess)
.flatMap(config -> this.configurationDAO.undo(config.configurationNodeId))
.flatMap(this.userActivityLogDAO::logUndo)
.getOrThrow();
}
@ -105,21 +104,6 @@ public class ConfigurationController extends EntityController<Configuration, Con
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
protected SqlTable getSQLTableOfEntity() {
return ConfigurationRecordDynamicSqlSupport.configurationRecord;

View file

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

View file

@ -69,6 +69,12 @@ public class ConfigurationValueController extends EntityController<Configuration
this.sebExamConfigService = sebExamConfigService;
}
@Override
protected Result<ConfigurationValue> logModify(final ConfigurationValue entity) {
// Skip the modify logging for each individual ConfigurationValue
return Result.of(entity);
}
@Override
protected ConfigurationValue createNew(final POSTMapper postParams) {
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)
.flatMap(this::validForCreate)
.flatMap(this.entityDAO::createNew)
.flatMap(this.userActivityLogDAO::logCreate)
.flatMap(this::logCreate)
.flatMap(this::notifyCreated)
.getOrThrow();
}
@ -297,7 +297,7 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
return this.checkModifyAccess(modifyData)
.flatMap(this::validForSave)
.flatMap(this.entityDAO::save)
.flatMap(this.userActivityLogDAO::logModify)
.flatMap(this::logModify)
.flatMap(this::notifySaved)
.getOrThrow();
}
@ -311,15 +311,16 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
method = RequestMethod.DELETE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public EntityProcessingReport hardDelete(@PathVariable final String modelId) {
final EntityType entityType = this.entityDAO.entityType();
final BulkAction bulkAction = new BulkAction(
BulkActionType.HARD_DELETE,
entityType,
new EntityKey(modelId, entityType));
return this.entityDAO.byModelId(modelId)
.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();
}
@ -451,6 +452,33 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
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
*
* @param postParams contains all post parameter from request

View file

@ -100,9 +100,9 @@ public class ExamMonitoringController {
@RequestParam final MultiValueMap<String, String> allRequestParams) {
this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
institutionId,
EntityType.EXAM);
EntityType.EXAM,
UserRole.EXAM_SUPPORTER);
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) {
this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
institutionId,
EntityType.EXAM);
EntityType.EXAM,
UserRole.EXAM_SUPPORTER);
return this.examSessionService
.getConnectionData(examId)
@ -159,9 +159,9 @@ public class ExamMonitoringController {
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
this.authorization.checkRole(
UserRole.EXAM_SUPPORTER,
institutionId,
EntityType.EXAM);
EntityType.EXAM,
UserRole.EXAM_SUPPORTER);
return this.examSessionService
.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;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.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.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.UserActivityLogRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
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;
@WebServiceProfile
@RestController
@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;
private final AuthorizationService authorization;
private final PaginationService paginationService;
public UserActivityLogController(
final UserActivityLogDAO userActivityLogDAO,
protected UserActivityLogController(
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;
this.authorization = authorization;
this.paginationService = paginationService;
super(
authorization,
bulkActionService,
entityDAO,
userActivityLogDAO,
paginationService,
beanValidationService);
}
@InitBinder
public void initBinder(final WebDataBinder binder) throws Exception {
this.authorization
.getUserService()
.addUsersInstitutionDefaultPropertySupport(binder);
@Override
protected void checkReadPrivilege(final Long institutionId) {
checkRead(institutionId);
}
/** Rest endpoint to get a Page UserActivityLog.
*
* GET /{api}/{entity-type-endpoint-name}
*
* 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();
@Override
protected Result<UserActivityLog> checkReadAccess(final UserActivityLog entity) {
return Result.of(entity);
}
private void checkBaseReadPrivilege(final Long institutionId) {
this.authorization.check(
PrivilegeType.READ,
@Override
protected boolean hasReadAccess(final UserActivityLog entity) {
return true;
}
@Override
protected SqlTable getSQLTableOfEntity() {
return UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord;
}
private void checkRead(final Long institution) {
this.authorization.checkRole(
institution,
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.webservice" 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.springframework.boot" 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.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" />
</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.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
################################
@ -956,7 +964,8 @@ sebserver.userlogs.list.column.institution=Institution
sebserver.userlogs.list.column.user=User
sebserver.userlogs.list.column.dateTime=Date
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.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.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 {
padding: 1px 3px 2px 3px;
background-color: #82be1e;

View file

@ -58,7 +58,7 @@ public class InstitutionTest {
json = jsonMapper.writeValueAsString(namesList);
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);
}

View file

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