SEBSERV-27 #Institution Form and actions, refactoring
This commit is contained in:
parent
5a9b85ccde
commit
377df32f72
61 changed files with 1222 additions and 190 deletions
13
pom.xml
13
pom.xml
|
@ -152,10 +152,10 @@
|
||||||
<includes>
|
<includes>
|
||||||
<include>ch/ethz/seb/sebserver/*</include>
|
<include>ch/ethz/seb/sebserver/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
<!-- <excludes> -->
|
<!-- <excludes> -->
|
||||||
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/mapper/*</exclude> -->
|
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/mapper/*</exclude> -->
|
||||||
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/model/*</exclude> -->
|
<!-- <exclude>ch/ethz/seb/sebserver/webservice/datalayer/batis/model/*</exclude> -->
|
||||||
<!-- </excludes> -->
|
<!-- </excludes> -->
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
@ -270,6 +270,11 @@
|
||||||
<artifactId>org.eclipse.rap.rwt</artifactId>
|
<artifactId>org.eclipse.rap.rwt</artifactId>
|
||||||
<version>3.5.0</version>
|
<version>3.5.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.rap</groupId>
|
||||||
|
<artifactId>org.eclipse.rap.fileupload</artifactId>
|
||||||
|
<version>3.7.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Misc -->
|
<!-- Misc -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -12,6 +12,21 @@ import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
|
||||||
|
|
||||||
|
/** SEB-Server (Safe Exam Browser Server) is a server component to maintain and support
|
||||||
|
* Exams running with SEB (Safe Exam Browser). TODO add link(s)
|
||||||
|
*
|
||||||
|
* SEB-Server uses Spring Boot as main framework is divided into two main components,
|
||||||
|
* a webservice component that implements the business logic, persistence management
|
||||||
|
* and defines a REST API to expose the services over HTTP. The second component is a
|
||||||
|
* GUI component built on RAP RWT/SWT that also uses Spring components to connect and use
|
||||||
|
* the webservice over HTTP. The two components are (implementation-wise) completely separated
|
||||||
|
* from each other by the Rest API and the webservice can also be used by another client.
|
||||||
|
* SEB-Server uses Spring's profiles to consequently separate sub-components of the webservice
|
||||||
|
* and GUI and can be used to start the components on separate servers or within the same
|
||||||
|
* server instance. Additional to the usual profiles like dev, prod, test there are combining
|
||||||
|
* profiles like dev-ws, dev-gui and prod-ws, prod-gui
|
||||||
|
*
|
||||||
|
* TODO documentation for presets to start all-in-one server or separated gui- and webservice- server */
|
||||||
@SpringBootApplication(exclude = {
|
@SpringBootApplication(exclude = {
|
||||||
// OAuth2ResourceServerAutoConfiguration.class,
|
// OAuth2ResourceServerAutoConfiguration.class,
|
||||||
UserDetailsServiceAutoConfiguration.class,
|
UserDetailsServiceAutoConfiguration.class,
|
||||||
|
|
|
@ -34,4 +34,9 @@ public class API {
|
||||||
|
|
||||||
public static final String INACTIVE_SUFFIX = "/inactive";
|
public static final String INACTIVE_SUFFIX = "/inactive";
|
||||||
|
|
||||||
|
public static final String PATH_VAR_MODEL_ID_NAME = "modelId";
|
||||||
|
public static final String PATH_VAR_MODEL_ID = "/{" + PATH_VAR_MODEL_ID_NAME + "}";
|
||||||
|
public static final String PATH_VAR_ACTIVE = PATH_VAR_MODEL_ID + ACTIVE_SUFFIX;
|
||||||
|
public static final String PATH_VAR_INACTIVE = PATH_VAR_MODEL_ID + INACTIVE_SUFFIX;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,20 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gbl.api;
|
package ch.ethz.seb.sebserver.gbl.api;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
@ -26,7 +32,23 @@ public class POSTMapper {
|
||||||
|
|
||||||
public POSTMapper(final MultiValueMap<String, String> params) {
|
public POSTMapper(final MultiValueMap<String, String> params) {
|
||||||
super();
|
super();
|
||||||
this.params = params;
|
this.params = new LinkedMultiValueMap<>();
|
||||||
|
if (params != null) {
|
||||||
|
for (final Map.Entry<String, List<String>> entry : params.entrySet()) {
|
||||||
|
this.params.put(
|
||||||
|
entry.getKey(),
|
||||||
|
entry.getValue()
|
||||||
|
.stream()
|
||||||
|
.map(encoded -> {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(encoded, "UTF-8");
|
||||||
|
} catch (final UnsupportedEncodingException e) {
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getString(final String name) {
|
public String getString(final String name) {
|
||||||
|
@ -117,4 +139,10 @@ public class POSTMapper {
|
||||||
return Utils.toDateTime(value);
|
return Utils.toDateTime(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends POSTMapper> T putIfAbsent(final String name, final String value) {
|
||||||
|
this.params.putIfAbsent(name, Arrays.asList(value));
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.service.page.content;
|
package ch.ethz.seb.sebserver.gbl.authorization;
|
||||||
|
|
||||||
public class Institution {
|
public class EntityPrivileges {
|
||||||
|
|
||||||
}
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
package ch.ethz.seb.sebserver.gbl.authorization;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
|
@ -15,6 +15,8 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
* institutional rights and ownershipRights. */
|
* institutional rights and ownershipRights. */
|
||||||
public final class Privilege {
|
public final class Privilege {
|
||||||
|
|
||||||
|
/** The RoleTypeKey defining the UserRole and EntityType for this Privilege */
|
||||||
|
public final RoleTypeKey roleTypeKey;
|
||||||
/** Defines a base-privilege type that defines the overall access for an entity-type */
|
/** Defines a base-privilege type that defines the overall access for an entity-type */
|
||||||
public final PrivilegeType privilegeType;
|
public final PrivilegeType privilegeType;
|
||||||
/** Defines an institutional privilege type that defines the institutional restricted access for a
|
/** Defines an institutional privilege type that defines the institutional restricted access for a
|
||||||
|
@ -24,10 +26,12 @@ public final class Privilege {
|
||||||
public final PrivilegeType ownershipPrivilege;
|
public final PrivilegeType ownershipPrivilege;
|
||||||
|
|
||||||
public Privilege(
|
public Privilege(
|
||||||
|
final RoleTypeKey roleTypeKey,
|
||||||
final PrivilegeType privilegeType,
|
final PrivilegeType privilegeType,
|
||||||
final PrivilegeType institutionalPrivilege,
|
final PrivilegeType institutionalPrivilege,
|
||||||
final PrivilegeType ownershipPrivilege) {
|
final PrivilegeType ownershipPrivilege) {
|
||||||
|
|
||||||
|
this.roleTypeKey = roleTypeKey;
|
||||||
this.privilegeType = privilegeType;
|
this.privilegeType = privilegeType;
|
||||||
this.institutionalPrivilege = institutionalPrivilege;
|
this.institutionalPrivilege = institutionalPrivilege;
|
||||||
this.ownershipPrivilege = ownershipPrivilege;
|
this.ownershipPrivilege = ownershipPrivilege;
|
||||||
|
@ -68,7 +72,7 @@ public final class Privilege {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A key that combines UserRole EntityType identity */
|
/** A key that combines UserRole EntityType identity */
|
||||||
static final class RoleTypeKey {
|
public static final class RoleTypeKey {
|
||||||
|
|
||||||
public final EntityType entityType;
|
public final EntityType entityType;
|
||||||
public final UserRole userRole;
|
public final UserRole userRole;
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* 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.authorization;
|
||||||
|
|
||||||
|
public enum PrivilegeLevel {
|
||||||
|
BASE,
|
||||||
|
INSTITUTIONAL,
|
||||||
|
OWNERSHIP
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
package ch.ethz.seb.sebserver.gbl.authorization;
|
||||||
|
|
||||||
/** Defines SEB-Server internal privilege types **/
|
/** Defines SEB-Server internal privilege types **/
|
||||||
public enum PrivilegeType {
|
public enum PrivilegeType {
|
|
@ -22,6 +22,11 @@ public interface Entity extends ModelIdAware {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
default EntityKey getEntityKey() {
|
||||||
|
return new EntityKey(getModelId(), entityType());
|
||||||
|
}
|
||||||
|
|
||||||
public static EntityName toName(final Entity entity) {
|
public static EntityName toName(final Entity entity) {
|
||||||
return new EntityName(
|
return new EntityName(
|
||||||
entity.entityType(),
|
entity.entityType(),
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gbl.model;
|
package ch.ethz.seb.sebserver.gbl.model;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
@ -15,8 +17,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
public class EntityKey {
|
public class EntityKey {
|
||||||
|
|
||||||
@JsonProperty(value = "modelId", required = true)
|
@JsonProperty(value = "modelId", required = true)
|
||||||
|
@NotNull
|
||||||
public final String modelId;
|
public final String modelId;
|
||||||
@JsonProperty(value = "entityType", required = true)
|
@JsonProperty(value = "entityType", required = true)
|
||||||
|
@NotNull
|
||||||
public final EntityType entityType;
|
public final EntityType entityType;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public final boolean isIdPK;
|
public final boolean isIdPK;
|
||||||
|
@ -26,6 +30,9 @@ public class EntityKey {
|
||||||
@JsonProperty(value = "modelId", required = true) final String modelId,
|
@JsonProperty(value = "modelId", required = true) final String modelId,
|
||||||
@JsonProperty(value = "entityType", required = true) final EntityType entityType) {
|
@JsonProperty(value = "entityType", required = true) final EntityType entityType) {
|
||||||
|
|
||||||
|
assert (modelId != null) : "modelId has null reference";
|
||||||
|
assert (entityType != null) : "entityType has null reference";
|
||||||
|
|
||||||
this.modelId = modelId;
|
this.modelId = modelId;
|
||||||
this.entityType = entityType;
|
this.entityType = entityType;
|
||||||
this.isIdPK = entityType != EntityType.USER;
|
this.isIdPK = entityType != EntityType.USER;
|
||||||
|
|
|
@ -12,11 +12,14 @@ import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class EntityProcessingReport {
|
public class EntityProcessingReport {
|
||||||
|
|
||||||
@JsonProperty(value = "source", required = true)
|
@JsonProperty(value = "source", required = true)
|
||||||
|
@ -37,6 +40,15 @@ public class EntityProcessingReport {
|
||||||
this.errors = Utils.immutableSetOf(errors);
|
this.errors = Utils.immutableSetOf(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public EntityKey getSingleSource() {
|
||||||
|
if (!this.source.isEmpty()) {
|
||||||
|
return this.source.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static final class ErrorEntry {
|
public static final class ErrorEntry {
|
||||||
|
|
||||||
public final EntityKey entityKey;
|
public final EntityKey entityKey;
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
package ch.ethz.seb.sebserver.gbl.model.institution;
|
package ch.ethz.seb.sebserver.gbl.model.institution;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
import javax.validation.constraints.Size;
|
import javax.validation.constraints.Size;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
|
@ -21,6 +23,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.INSTITUTION;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public final class Institution implements GrantEntity, Activatable {
|
public final class Institution implements GrantEntity, Activatable {
|
||||||
|
|
||||||
@JsonProperty(Domain.ATTR_ID)
|
@JsonProperty(Domain.ATTR_ID)
|
||||||
|
@ -32,7 +35,7 @@ public final class Institution implements GrantEntity, Activatable {
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@JsonProperty(INSTITUTION.ATTR_URL_SUFFIX)
|
@JsonProperty(INSTITUTION.ATTR_URL_SUFFIX)
|
||||||
@Size(min = 3, max = 255, message = "institution:urlSuffix:size:{min}:{max}:${validatedValue}")
|
@Pattern(regexp = "(^$|.{3,255})", message = "institution:urlSuffix:size:{min}:{max}:${validatedValue}")
|
||||||
public final String urlSuffix;
|
public final String urlSuffix;
|
||||||
|
|
||||||
@JsonProperty(INSTITUTION.ATTR_LOGO_IMAGE)
|
@JsonProperty(INSTITUTION.ATTR_LOGO_IMAGE)
|
||||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.page;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
import org.eclipse.swt.widgets.Shell;
|
import org.eclipse.swt.widgets.Shell;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
|
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
|
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection;
|
import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection;
|
||||||
|
@ -33,7 +34,8 @@ public interface PageContext {
|
||||||
|
|
||||||
public static final String PAGE_TEMPLATE_COMPOSER_NAME = "ATTR_PAGE_TEMPLATE_COMPOSER_NAME";
|
public static final String PAGE_TEMPLATE_COMPOSER_NAME = "ATTR_PAGE_TEMPLATE_COMPOSER_NAME";
|
||||||
|
|
||||||
public static final String INSTITUTION_ID = "INSTITUTION_ID";
|
public static final String READ_ONLY = "READ_ONLY";
|
||||||
|
public static final String CREATE_NEW = "CREATE_NEW";
|
||||||
|
|
||||||
public static final String ENTITY_ID = "ENTITY_ID";
|
public static final String ENTITY_ID = "ENTITY_ID";
|
||||||
public static final String PARENT_ENTITY_ID = "PARENT_ENTITY_ID";
|
public static final String PARENT_ENTITY_ID = "PARENT_ENTITY_ID";
|
||||||
|
@ -55,8 +57,7 @@ public interface PageContext {
|
||||||
//
|
//
|
||||||
// public static final String AUTHORIZATION_CONTEXT = "AUTHORIZATION_CONTEXT";
|
// public static final String AUTHORIZATION_CONTEXT = "AUTHORIZATION_CONTEXT";
|
||||||
// public static final String AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER";
|
// public static final String AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER";
|
||||||
public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
|
// public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
|
||||||
public static final String LGOUT_SUCCESS = "LGOUT_SUCCESS";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,16 +93,30 @@ public interface PageContext {
|
||||||
* @param key the key of the attribute
|
* @param key the key of the attribute
|
||||||
* @param value the value of the attribute
|
* @param value the value of the attribute
|
||||||
* @return this PageContext instance (builder pattern) */
|
* @return this PageContext instance (builder pattern) */
|
||||||
PageContext withAttr(String key, String value);
|
PageContext withAttribute(String key, String value);
|
||||||
|
|
||||||
PageContext withSelection(ActivitySelection selection);
|
default PageContext withSelection(final ActivitySelection selection) {
|
||||||
|
return withSelection(selection, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
PageContext withSelection(ActivitySelection selection, boolean clearAttributes);
|
||||||
|
|
||||||
String getAttribute(String name);
|
String getAttribute(String name);
|
||||||
|
|
||||||
String getAttribute(String name, String def);
|
String getAttribute(String name, String def);
|
||||||
|
|
||||||
|
EntityKey getEntityKey();
|
||||||
|
|
||||||
|
EntityKey getParentEntityKey();
|
||||||
|
|
||||||
|
PageContext withEntityKey(EntityKey entityKey);
|
||||||
|
|
||||||
|
PageContext withParentEntityKey(EntityKey entityKey);
|
||||||
|
|
||||||
boolean hasAttribute(String name);
|
boolean hasAttribute(String name);
|
||||||
|
|
||||||
|
PageContext removeAttribute(String name);
|
||||||
|
|
||||||
/** Publishes a given PageEvent to the current page tree
|
/** Publishes a given PageEvent to the current page tree
|
||||||
* This goes through the page-tree and collects all listeners the are listen to
|
* This goes through the page-tree and collects all listeners the are listen to
|
||||||
* the specified page event type.
|
* the specified page event type.
|
||||||
|
@ -133,7 +148,7 @@ public interface PageContext {
|
||||||
* @param error the error as Throwable */
|
* @param error the error as Throwable */
|
||||||
void notifyError(String errorMessage, Throwable error);
|
void notifyError(String errorMessage, Throwable error);
|
||||||
|
|
||||||
void notifyError(Throwable error);
|
<T> T notifyError(Throwable error);
|
||||||
|
|
||||||
<T> T logoutOnError(Throwable error);
|
<T> T logoutOnError(Throwable error);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
package ch.ethz.seb.sebserver.gui.service.page.action;
|
package ch.ethz.seb.sebserver.gui.service.page.action;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ -23,12 +24,13 @@ import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent;
|
import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||||
|
|
||||||
public class Action implements Runnable {
|
public final class Action implements Runnable {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(Action.class);
|
private static final Logger log = LoggerFactory.getLogger(Action.class);
|
||||||
|
|
||||||
public final ActionDefinition definition;
|
public final ActionDefinition definition;
|
||||||
String confirmationMessage;
|
String confirmationMessage;
|
||||||
|
BooleanSupplier confirmComdition = () -> true;
|
||||||
String successMessage;
|
String successMessage;
|
||||||
boolean updateOnSelection;
|
boolean updateOnSelection;
|
||||||
|
|
||||||
|
@ -49,7 +51,7 @@ public class Action implements Runnable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (StringUtils.isNotBlank(this.confirmationMessage)) {
|
if (StringUtils.isNotBlank(this.confirmationMessage) && this.confirmComdition.getAsBoolean()) {
|
||||||
this.pageContext.applyConfirmDialog(
|
this.pageContext.applyConfirmDialog(
|
||||||
this.confirmationMessage,
|
this.confirmationMessage,
|
||||||
() -> exec());
|
() -> exec());
|
||||||
|
|
|
@ -16,16 +16,32 @@ public enum ActionDefinition {
|
||||||
"sebserver.institution.action.new",
|
"sebserver.institution.action.new",
|
||||||
IconButtonType.NEW_ACTION),
|
IconButtonType.NEW_ACTION),
|
||||||
|
|
||||||
|
INSTITUTION_VIEW(
|
||||||
|
"sebserver.institution.action.view",
|
||||||
|
IconButtonType.VIEW_ACTION),
|
||||||
|
|
||||||
INSTITUTION_MODIFY(
|
INSTITUTION_MODIFY(
|
||||||
"sebserver.institution.action.modify",
|
"sebserver.institution.action.modify",
|
||||||
IconButtonType.MODIFY_ACTION),
|
IconButtonType.MODIFY_ACTION),
|
||||||
|
|
||||||
|
INSTITUTION_CANCEL_MODIFY(
|
||||||
|
"sebserver.overall.action.modify.cancel",
|
||||||
|
IconButtonType.CANCEL_ACTION),
|
||||||
|
|
||||||
INSTITUTION_SAVE(
|
INSTITUTION_SAVE(
|
||||||
"actions.modify.institution",
|
"sebserver.institution.action.save",
|
||||||
IconButtonType.SAVE_ACTION),
|
IconButtonType.SAVE_ACTION),
|
||||||
|
|
||||||
|
INSTITUTION_ACTIVATE(
|
||||||
|
"sebserver.institution.action.activate",
|
||||||
|
IconButtonType.ACTIVATE_ACTION),
|
||||||
|
|
||||||
|
INSTITUTION_DEACTIVATE(
|
||||||
|
"sebserver.institution.action.deactivate",
|
||||||
|
IconButtonType.DEACTIVATE_ACTION),
|
||||||
|
|
||||||
INSTITUTION_DELETE(
|
INSTITUTION_DELETE(
|
||||||
"actions.delete.institution",
|
"sebserver.institution.action.modify",
|
||||||
IconButtonType.DELETE_ACTION),
|
IconButtonType.DELETE_ACTION),
|
||||||
|
|
||||||
LMS_SETUP_NEW(
|
LMS_SETUP_NEW(
|
||||||
|
|
|
@ -13,67 +13,92 @@ import static ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection.
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent;
|
import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.NewInstitution;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution;
|
||||||
import ch.ethz.seb.sebserver.gui.service.table.EntityTable;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.DeactivateInstitution;
|
||||||
|
|
||||||
public final class InstitutionActions {
|
public final class InstitutionActions {
|
||||||
|
|
||||||
public static Result<?> newInstitution(final Action action) {
|
public static Function<Institution, Institution> postSaveAdapter(final PageContext pageContext) {
|
||||||
return action.restService
|
return inst -> {
|
||||||
.getBuilder(NewInstitution.class)
|
goToInstitution(pageContext, inst.getModelId(), false);
|
||||||
.call();
|
return inst;
|
||||||
}
|
|
||||||
|
|
||||||
public static Function<Action, Result<?>> editInstitution(final EntityTable<?> fromTable) {
|
|
||||||
return action -> {
|
|
||||||
final Collection<String> selection = fromTable.getSelection();
|
|
||||||
if (selection.isEmpty()) {
|
|
||||||
return Result.ofError(new PageMessageException("sebserver.institution.info.pleaseSelect"));
|
|
||||||
}
|
|
||||||
|
|
||||||
final EntityKey entityKey = new EntityKey(
|
|
||||||
selection.iterator().next(),
|
|
||||||
EntityType.INSTITUTION);
|
|
||||||
action.pageContext.publishPageEvent(new ActivitySelectionEvent(
|
|
||||||
INSTITUTION_NODE
|
|
||||||
.createSelection()
|
|
||||||
.withEntity(entityKey)));
|
|
||||||
|
|
||||||
return Result.of(entityKey);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// /** Use this higher-order function to create a new Institution action function.
|
public static Result<?> newInstitution(final Action action) {
|
||||||
// *
|
return Result.of(goToInstitution(action.pageContext, null, true));
|
||||||
// * @return */
|
}
|
||||||
// static Runnable newInstitution(final PageContext composerCtx, final RestServices restServices) {
|
|
||||||
// return () -> {
|
public static Result<?> viewInstitution(final Action action) {
|
||||||
// final IdAndName newInstitutionId = restServices
|
return fromInstitution(action, false);
|
||||||
// .sebServerAPICall(NewInstitution.class)
|
}
|
||||||
// .doAPICall()
|
|
||||||
// .onErrorThrow("Unexpected Error");
|
public static Result<?> editInstitutionFromList(final Action action) {
|
||||||
// composerCtx.notify(new ActionEvent(ActionDefinition.INSTITUTION_NEW, newInstitutionId));
|
return fromInstitution(action, true);
|
||||||
// };
|
}
|
||||||
// }
|
|
||||||
//
|
public static Result<?> editInstitution(final Action action) {
|
||||||
// /** Use this higher-order function to create a delete Institution action function.
|
return Result.of(goToInstitution(
|
||||||
// *
|
action.pageContext,
|
||||||
// * @return */
|
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID),
|
||||||
// static Runnable deleteInstitution(final PageContext composerCtx, final RestServices restServices,
|
true));
|
||||||
// final String instId) {
|
}
|
||||||
// return () -> {
|
|
||||||
// restServices
|
public static Result<?> cancelEditInstitution(final Action action) {
|
||||||
// .sebServerAPICall(DeleteInstitution.class)
|
return Result.of(goToInstitution(
|
||||||
// .attribute(AttributeKeys.INSTITUTION_ID, instId)
|
action.pageContext,
|
||||||
// .doAPICall()
|
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID),
|
||||||
// .onErrorThrow("Unexpected Error");
|
false));
|
||||||
// composerCtx.notify(new ActionEvent(ActionDefinition.INSTITUTION_DELETE, instId));
|
}
|
||||||
// };
|
|
||||||
// }
|
public static Result<?> activateInstitution(final Action action) {
|
||||||
|
return action.restService
|
||||||
|
.getBuilder(ActivateInstitution.class)
|
||||||
|
.withURIVariable(
|
||||||
|
API.PATH_VAR_MODEL_ID_NAME,
|
||||||
|
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID))
|
||||||
|
.call()
|
||||||
|
.map(report -> goToInstitution(action.pageContext, report.getSingleSource().modelId, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<?> deactivateInstitution(final Action action) {
|
||||||
|
return action.restService
|
||||||
|
.getBuilder(DeactivateInstitution.class)
|
||||||
|
.withURIVariable(
|
||||||
|
API.PATH_VAR_MODEL_ID_NAME,
|
||||||
|
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID))
|
||||||
|
.call()
|
||||||
|
.map(report -> goToInstitution(action.pageContext, report.getSingleSource().modelId, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<?> fromInstitution(final Action action, final boolean edit) {
|
||||||
|
final Collection<String> selection = action.selectionSupplier.get();
|
||||||
|
if (selection.isEmpty()) {
|
||||||
|
return Result.ofError(new PageMessageException("sebserver.institution.info.pleaseSelect"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.of(goToInstitution(action.pageContext, selection.iterator().next(), edit));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActivitySelection goToInstitution(final PageContext pageContext, final String modelId,
|
||||||
|
final boolean edit) {
|
||||||
|
final ActivitySelection activitySelection = INSTITUTION_NODE
|
||||||
|
.createSelection()
|
||||||
|
.withEntity(new EntityKey(modelId, EntityType.INSTITUTION))
|
||||||
|
.withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit))
|
||||||
|
.withAttribute(AttributeKeys.CREATE_NEW, (modelId != null) ? "false" : "true");
|
||||||
|
pageContext.publishPageEvent(new ActivitySelectionEvent(activitySelection));
|
||||||
|
return activitySelection;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener;
|
import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.MainPageState;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.MainPageState;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||||
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
||||||
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
|
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
|
||||||
|
|
||||||
|
@ -43,22 +43,25 @@ import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
|
||||||
@Component
|
@Component
|
||||||
public class ActivitiesPane implements TemplateComposer {
|
public class ActivitiesPane implements TemplateComposer {
|
||||||
|
|
||||||
|
private static final String ATTR_ACTIVITY_SELECTION = "ACTIVITY_SELECTION";
|
||||||
|
|
||||||
private final WidgetFactory widgetFactory;
|
private final WidgetFactory widgetFactory;
|
||||||
private final RestService restService;
|
private final RestService restService;
|
||||||
private final AuthorizationContextHolder authorizationContextHolder;
|
private final CurrentUser currentUser;
|
||||||
|
|
||||||
|
// TODO are those really needed?
|
||||||
private final Map<ActionDefinition, ActivityActionHandler> activityActionHandler =
|
private final Map<ActionDefinition, ActivityActionHandler> activityActionHandler =
|
||||||
new EnumMap<>(ActionDefinition.class);
|
new EnumMap<>(ActionDefinition.class);
|
||||||
|
|
||||||
public ActivitiesPane(
|
public ActivitiesPane(
|
||||||
final WidgetFactory widgetFactory,
|
final WidgetFactory widgetFactory,
|
||||||
final RestService restService,
|
final RestService restService,
|
||||||
final AuthorizationContextHolder authorizationContextHolder,
|
final CurrentUser currentUser,
|
||||||
final Collection<ActivityActionHandler> activityActionHandler) {
|
final Collection<ActivityActionHandler> activityActionHandler) {
|
||||||
|
|
||||||
this.widgetFactory = widgetFactory;
|
this.widgetFactory = widgetFactory;
|
||||||
this.restService = restService;
|
this.restService = restService;
|
||||||
this.authorizationContextHolder = authorizationContextHolder;
|
this.currentUser = currentUser;
|
||||||
|
|
||||||
for (final ActivityActionHandler aah : activityActionHandler) {
|
for (final ActivityActionHandler aah : activityActionHandler) {
|
||||||
this.activityActionHandler.put(aah.handlesAction(), aah);
|
this.activityActionHandler.put(aah.handlesAction(), aah);
|
||||||
|
@ -67,10 +70,8 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compose(final PageContext pageContext) {
|
public void compose(final PageContext pageContext) {
|
||||||
final UserInfo userInfo = this.authorizationContextHolder
|
final UserInfo userInfo = this.currentUser
|
||||||
.getAuthorizationContext()
|
.getOrHandleError(pageContext::logoutOnError);
|
||||||
.getLoggedInUser()
|
|
||||||
.get(pageContext::logoutOnError);
|
|
||||||
|
|
||||||
final Label activities = this.widgetFactory.labelLocalized(
|
final Label activities = this.widgetFactory.labelLocalized(
|
||||||
pageContext.getParent(),
|
pageContext.getParent(),
|
||||||
|
@ -80,8 +81,9 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
activitiesGridData.horizontalIndent = 20;
|
activitiesGridData.horizontalIndent = 20;
|
||||||
activities.setLayoutData(activitiesGridData);
|
activities.setLayoutData(activitiesGridData);
|
||||||
|
|
||||||
final Tree navigation =
|
final Tree navigation = this.widgetFactory.treeLocalized(
|
||||||
this.widgetFactory.treeLocalized(pageContext.getParent(), SWT.SINGLE | SWT.FULL_SELECTION);
|
pageContext.getParent(),
|
||||||
|
SWT.SINGLE | SWT.FULL_SELECTION);
|
||||||
final GridData navigationGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
final GridData navigationGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||||
navigationGridData.horizontalIndent = 10;
|
navigationGridData.horizontalIndent = 10;
|
||||||
navigation.setLayoutData(navigationGridData);
|
navigation.setLayoutData(navigationGridData);
|
||||||
|
@ -96,7 +98,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
||||||
navigation,
|
navigation,
|
||||||
Activity.INSTITUTION_ROOT.title);
|
Activity.INSTITUTION_ROOT.title);
|
||||||
ActivitySelection.inject(institutions, Activity.INSTITUTION_ROOT.createSelection());
|
injectActivitySelection(institutions, Activity.INSTITUTION_ROOT.createSelection());
|
||||||
|
|
||||||
// for (final EntityName inst : insitutionNames) {
|
// for (final EntityName inst : insitutionNames) {
|
||||||
// createInstitutionItem(institutions, inst);
|
// createInstitutionItem(institutions, inst);
|
||||||
|
@ -106,7 +108,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
||||||
navigation,
|
navigation,
|
||||||
Activity.INSTITUTION_ROOT.title);
|
Activity.INSTITUTION_ROOT.title);
|
||||||
ActivitySelection.inject(institutions, Activity.INSTITUTION_NODE.createSelection());
|
injectActivitySelection(institutions, Activity.INSTITUTION_NODE.createSelection());
|
||||||
// final EntityName inst = insitutionNames.iterator().next();
|
// final EntityName inst = insitutionNames.iterator().next();
|
||||||
// createInstitutionItem(navigation, inst);
|
// createInstitutionItem(navigation, inst);
|
||||||
}
|
}
|
||||||
|
@ -155,16 +157,20 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
|
|
||||||
navigation.addListener(SWT.Expand, this::handleExpand);
|
navigation.addListener(SWT.Expand, this::handleExpand);
|
||||||
navigation.addListener(SWT.Selection, event -> handleSelection(pageContext, event));
|
navigation.addListener(SWT.Selection, event -> handleSelection(pageContext, event));
|
||||||
|
|
||||||
navigation.setData(
|
navigation.setData(
|
||||||
PageEventListener.LISTENER_ATTRIBUTE_KEY,
|
PageEventListener.LISTENER_ATTRIBUTE_KEY,
|
||||||
new ActionEventListener() {
|
new ActionEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void notify(final ActionEvent event) {
|
public void notify(final ActionEvent event) {
|
||||||
final ActivityActionHandler aah =
|
// final ActivityActionHandler aah =
|
||||||
ActivitiesPane.this.activityActionHandler.get(event.actionDefinition);
|
// ActivitiesPane.this.activityActionHandler.get(event.actionDefinition);
|
||||||
if (aah != null) {
|
// if (aah != null) {
|
||||||
aah.notifyAction(event, navigation, pageContext);
|
// aah.notifyAction(event, navigation, pageContext);
|
||||||
|
// }
|
||||||
|
// on case of an Action with ActivitySelection, reset the MainPageState
|
||||||
|
if (event.source instanceof ActivitySelection) {
|
||||||
|
final MainPageState mainPageState = MainPageState.get();
|
||||||
|
mainPageState.activitySelection = (ActivitySelection) event.source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -172,11 +178,13 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
// page-selection on (re)load
|
// page-selection on (re)load
|
||||||
final MainPageState mainPageState = MainPageState.get();
|
final MainPageState mainPageState = MainPageState.get();
|
||||||
|
|
||||||
if (mainPageState.activitySelection == null) {
|
if (mainPageState.activitySelection == null ||
|
||||||
mainPageState.activitySelection = ActivitySelection.get(navigation.getItem(0));
|
mainPageState.activitySelection.activity == ActivitySelection.Activity.NONE) {
|
||||||
|
mainPageState.activitySelection = getActivitySelection(navigation.getItem(0));
|
||||||
}
|
}
|
||||||
pageContext.publishPageEvent(
|
pageContext.publishPageEvent(
|
||||||
new ActivitySelectionEvent(mainPageState.activitySelection));
|
new ActivitySelectionEvent(mainPageState.activitySelection));
|
||||||
|
navigation.select(navigation.getItem(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void runningExamExpand(final TreeItem item) {
|
// private void runningExamExpand(final TreeItem item) {
|
||||||
|
@ -202,7 +210,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
|
|
||||||
System.out.println("opened: " + treeItem);
|
System.out.println("opened: " + treeItem);
|
||||||
|
|
||||||
final ActivitySelection activity = ActivitySelection.get(treeItem);
|
final ActivitySelection activity = getActivitySelection(treeItem);
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
activity.processExpand(treeItem);
|
activity.processExpand(treeItem);
|
||||||
}
|
}
|
||||||
|
@ -214,7 +222,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
System.out.println("selected: " + treeItem);
|
System.out.println("selected: " + treeItem);
|
||||||
|
|
||||||
final MainPageState mainPageState = MainPageState.get();
|
final MainPageState mainPageState = MainPageState.get();
|
||||||
final ActivitySelection activitySelection = ActivitySelection.get(treeItem);
|
final ActivitySelection activitySelection = getActivitySelection(treeItem);
|
||||||
if (mainPageState.activitySelection == null) {
|
if (mainPageState.activitySelection == null) {
|
||||||
mainPageState.activitySelection = Activity.NONE.createSelection();
|
mainPageState.activitySelection = Activity.NONE.createSelection();
|
||||||
}
|
}
|
||||||
|
@ -239,7 +247,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
|
|
||||||
static void createInstitutionItem(final EntityName entityName, final TreeItem institution) {
|
static void createInstitutionItem(final EntityName entityName, final TreeItem institution) {
|
||||||
institution.setText(entityName.name);
|
institution.setText(entityName.name);
|
||||||
ActivitySelection.inject(
|
injectActivitySelection(
|
||||||
institution,
|
institution,
|
||||||
Activity.INSTITUTION_NODE
|
Activity.INSTITUTION_NODE
|
||||||
.createSelection()
|
.createSelection()
|
||||||
|
@ -256,7 +264,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final TreeItem item : items) {
|
for (final TreeItem item : items) {
|
||||||
final ActivitySelection activitySelection = ActivitySelection.get(item);
|
final ActivitySelection activitySelection = getActivitySelection(item);
|
||||||
final String id = activitySelection.getEntityId();
|
final String id = activitySelection.getEntityId();
|
||||||
if (activitySelection != null && activitySelection.activity == activity &&
|
if (activitySelection != null && activitySelection.activity == activity &&
|
||||||
(id == null || (objectId != null && objectId.equals(id)))) {
|
(id == null || (objectId != null && objectId.equals(id)))) {
|
||||||
|
@ -285,4 +293,12 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
expand(item.getParentItem());
|
expand(item.getParentItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ActivitySelection getActivitySelection(final TreeItem item) {
|
||||||
|
return (ActivitySelection) item.getData(ATTR_ACTIVITY_SELECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void injectActivitySelection(final TreeItem item, final ActivitySelection selection) {
|
||||||
|
item.setData(ATTR_ACTIVITY_SELECTION, selection);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.action.ActionPane;
|
import ch.ethz.seb.sebserver.gui.service.page.action.ActionPane;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.content.InstitutionForm;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.content.InstitutionList;
|
import ch.ethz.seb.sebserver.gui.service.page.content.InstitutionList;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.TODOTemplate;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.TODOTemplate;
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ public class ActivitySelection {
|
||||||
ActionPane.class,
|
ActionPane.class,
|
||||||
new LocTextKey("sebserver.activities.inst")),
|
new LocTextKey("sebserver.activities.inst")),
|
||||||
INSTITUTION_NODE(
|
INSTITUTION_NODE(
|
||||||
TODOTemplate.class,
|
InstitutionForm.class,
|
||||||
ActionPane.class,
|
ActionPane.class,
|
||||||
new LocTextKey("sebserver.activities.inst")),
|
new LocTextKey("sebserver.activities.inst")),
|
||||||
//
|
//
|
||||||
|
@ -84,8 +85,6 @@ public class ActivitySelection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String ATTR_ACTIVITY_SELECTION = "ACTIVITY_SELECTION";
|
|
||||||
|
|
||||||
public final Activity activity;
|
public final Activity activity;
|
||||||
final Map<String, String> attributes;
|
final Map<String, String> attributes;
|
||||||
Consumer<TreeItem> expandFunction = EMPTY_FUNCTION;
|
Consumer<TreeItem> expandFunction = EMPTY_FUNCTION;
|
||||||
|
@ -114,6 +113,11 @@ public class ActivitySelection {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ActivitySelection withAttribute(final String name, final String value) {
|
||||||
|
this.attributes.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getAttributes() {
|
public Map<String, String> getAttributes() {
|
||||||
return Collections.unmodifiableMap(this.attributes);
|
return Collections.unmodifiableMap(this.attributes);
|
||||||
}
|
}
|
||||||
|
@ -134,12 +138,4 @@ public class ActivitySelection {
|
||||||
return this.attributes.get(AttributeKeys.ENTITY_ID);
|
return this.attributes.get(AttributeKeys.ENTITY_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ActivitySelection get(final TreeItem item) {
|
|
||||||
return (ActivitySelection) item.getData(ATTR_ACTIVITY_SELECTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void inject(final TreeItem item, final ActivitySelection selection) {
|
|
||||||
item.setData(ATTR_ACTIVITY_SELECTION, selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gui.service.page.content;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
import org.eclipse.swt.SWT;
|
||||||
|
import org.eclipse.swt.layout.GridData;
|
||||||
|
import org.eclipse.swt.layout.GridLayout;
|
||||||
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.Label;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.action.InstitutionActions;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEventListener;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.form.FormHandle;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.form.PageFormService;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.NewInstitution;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.SaveInstitution;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@GuiProfile
|
||||||
|
public class InstitutionForm implements TemplateComposer {
|
||||||
|
|
||||||
|
private final PageFormService pageFormService;
|
||||||
|
private final RestService restService;
|
||||||
|
|
||||||
|
protected InstitutionForm(final PageFormService pageFormService, final RestService restService) {
|
||||||
|
this.pageFormService = pageFormService;
|
||||||
|
this.restService = restService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compose(final PageContext pageContext) {
|
||||||
|
final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory();
|
||||||
|
|
||||||
|
final boolean readonly = BooleanUtils.toBoolean(
|
||||||
|
pageContext.getAttribute(AttributeKeys.READ_ONLY, "true"));
|
||||||
|
final boolean createNew = BooleanUtils.toBoolean(
|
||||||
|
pageContext.getAttribute(AttributeKeys.CREATE_NEW, "false"));
|
||||||
|
|
||||||
|
// get data or create new and handle error
|
||||||
|
Institution institution = null;
|
||||||
|
PageContext formContext = pageContext;
|
||||||
|
|
||||||
|
if (createNew) {
|
||||||
|
institution = this.restService
|
||||||
|
.getBuilder(NewInstitution.class)
|
||||||
|
.withQueryParam(Domain.INSTITUTION.ATTR_NAME, "[NEW-" + UUID.randomUUID() + "]")
|
||||||
|
.call()
|
||||||
|
.get(pageContext::notifyError);
|
||||||
|
formContext = pageContext.withEntityKey(institution.getEntityKey());
|
||||||
|
} else {
|
||||||
|
final String instId = pageContext.getAttribute(AttributeKeys.ENTITY_ID);
|
||||||
|
institution = this.restService
|
||||||
|
.getBuilder(GetInstitution.class)
|
||||||
|
.withURIVariable(API.PATH_VAR_MODEL_ID_NAME, instId)
|
||||||
|
.call()
|
||||||
|
.get(pageContext::notifyError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (institution == null) {
|
||||||
|
// TODO should here be a forward to institution list page for SEB Admin?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// page grid
|
||||||
|
final Composite content = new Composite(formContext.getParent(), SWT.NONE);
|
||||||
|
final GridLayout contentLayout = new GridLayout();
|
||||||
|
contentLayout.marginLeft = 10;
|
||||||
|
content.setLayout(contentLayout);
|
||||||
|
content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||||
|
|
||||||
|
// title
|
||||||
|
final Label pageTitle = widgetFactory.labelLocalizedTitle(
|
||||||
|
content, new LocTextKey(
|
||||||
|
"sebserver.institution.form.title",
|
||||||
|
institution.name));
|
||||||
|
|
||||||
|
pageTitle.setLayoutData(new GridData(SWT.TOP, SWT.LEFT, true, false));
|
||||||
|
ActionEventListener.injectListener(
|
||||||
|
pageTitle,
|
||||||
|
ActionDefinition.INSTITUTION_SAVE,
|
||||||
|
event -> {
|
||||||
|
final Entity entity = (Entity) event.source;
|
||||||
|
widgetFactory.injectI18n(pageTitle, new LocTextKey(
|
||||||
|
"sebserver.institution.form.title",
|
||||||
|
entity.getName()));
|
||||||
|
content.layout();
|
||||||
|
});
|
||||||
|
|
||||||
|
// The Institution form
|
||||||
|
final FormHandle<Institution> formHandle = this.pageFormService.getBuilder(
|
||||||
|
formContext.copyOf(content), 4)
|
||||||
|
.readonly(readonly)
|
||||||
|
.putStaticValue("id", institution.getModelId())
|
||||||
|
.addTextField(
|
||||||
|
Domain.INSTITUTION.ATTR_NAME,
|
||||||
|
"sebserver.institution.form.name",
|
||||||
|
institution.name, 2)
|
||||||
|
.addEmptyCell()
|
||||||
|
.addTextField(
|
||||||
|
Domain.INSTITUTION.ATTR_URL_SUFFIX,
|
||||||
|
"sebserver.institution.form.urlSuffix",
|
||||||
|
institution.urlSuffix, 2)
|
||||||
|
.addEmptyCell()
|
||||||
|
.addImageUpload(
|
||||||
|
Domain.INSTITUTION.ATTR_LOGO_IMAGE,
|
||||||
|
"sebserver.institution.form.logoImage",
|
||||||
|
institution.logoImage, 2)
|
||||||
|
.addEmptyCell()
|
||||||
|
.addTextField(
|
||||||
|
Domain.INSTITUTION.ATTR_URL_SUFFIX,
|
||||||
|
"sebserver.institution.form.urlSuffix",
|
||||||
|
institution.urlSuffix, 2)
|
||||||
|
.buildFor(
|
||||||
|
this.restService.getRestCall(SaveInstitution.class),
|
||||||
|
InstitutionActions.postSaveAdapter(pageContext));
|
||||||
|
|
||||||
|
// propagate content actions to action-pane
|
||||||
|
if (readonly) {
|
||||||
|
formContext.createAction(ActionDefinition.INSTITUTION_NEW)
|
||||||
|
.withExec(InstitutionActions::newInstitution)
|
||||||
|
.publish()
|
||||||
|
.createAction(ActionDefinition.INSTITUTION_MODIFY)
|
||||||
|
.withExec(InstitutionActions::editInstitution)
|
||||||
|
.publish();
|
||||||
|
if (!institution.isActive()) {
|
||||||
|
formContext.createAction(ActionDefinition.INSTITUTION_ACTIVATE)
|
||||||
|
.withExec(InstitutionActions::activateInstitution)
|
||||||
|
.publish();
|
||||||
|
} else {
|
||||||
|
formContext.createAction(ActionDefinition.INSTITUTION_DEACTIVATE)
|
||||||
|
.withExec(InstitutionActions::deactivateInstitution)
|
||||||
|
.publish();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formContext.createAction(ActionDefinition.INSTITUTION_SAVE)
|
||||||
|
.withExec(formHandle::postChanges)
|
||||||
|
.publish()
|
||||||
|
.createAction(ActionDefinition.INSTITUTION_CANCEL_MODIFY)
|
||||||
|
.withExec(InstitutionActions::cancelEditInstitution)
|
||||||
|
.withConfirm("sebserver.overall.action.modify.cancel.confirm")
|
||||||
|
.publish();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -83,8 +83,13 @@ public class InstitutionList implements TemplateComposer {
|
||||||
pageContext.createAction(ActionDefinition.INSTITUTION_NEW)
|
pageContext.createAction(ActionDefinition.INSTITUTION_NEW)
|
||||||
.withExec(InstitutionActions::newInstitution)
|
.withExec(InstitutionActions::newInstitution)
|
||||||
.publish()
|
.publish()
|
||||||
|
.createAction(ActionDefinition.INSTITUTION_VIEW)
|
||||||
|
.withSelectionSupplier(table::getSelection)
|
||||||
|
.withExec(InstitutionActions::viewInstitution)
|
||||||
|
.publish()
|
||||||
.createAction(ActionDefinition.INSTITUTION_MODIFY)
|
.createAction(ActionDefinition.INSTITUTION_MODIFY)
|
||||||
.withExec(InstitutionActions.editInstitution(table))
|
.withSelectionSupplier(table::getSelection)
|
||||||
|
.withExec(InstitutionActions::editInstitutionFromList)
|
||||||
.publish();
|
.publish();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,15 @@ public interface ActionEventListener extends PageEventListener<ActionEvent> {
|
||||||
return type == ActionEvent.class;
|
return type == ActionEvent.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionEventListener of(final Consumer<ActionEvent> eventConsumer) {
|
// static ActionEventListener of(final Consumer<ActionEvent> eventConsumer) {
|
||||||
return new ActionEventListener() {
|
// return new ActionEventListener() {
|
||||||
@Override
|
// @Override
|
||||||
public void notify(final ActionEvent event) {
|
// public void notify(final ActionEvent event) {
|
||||||
eventConsumer.accept(event);
|
// eventConsumer.accept(event);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
static ActionEventListener of(
|
static ActionEventListener of(
|
||||||
final Predicate<ActionEvent> predicate,
|
final Predicate<ActionEvent> predicate,
|
||||||
|
|
|
@ -27,7 +27,9 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.widget.ImageUpload;
|
||||||
import ch.ethz.seb.sebserver.gui.service.widget.SingleSelection;
|
import ch.ethz.seb.sebserver.gui.service.widget.SingleSelection;
|
||||||
|
|
||||||
public final class Form implements FormBinding {
|
public final class Form implements FormBinding {
|
||||||
|
@ -40,9 +42,17 @@ public final class Form implements FormBinding {
|
||||||
private final Map<String, List<Form>> subLists = new LinkedHashMap<>();
|
private final Map<String, List<Form>> subLists = new LinkedHashMap<>();
|
||||||
private final Map<String, Set<String>> groups = new LinkedHashMap<>();
|
private final Map<String, Set<String>> groups = new LinkedHashMap<>();
|
||||||
|
|
||||||
Form(final JSONMapper jsonMapper) {
|
private final EntityKey entityKey;
|
||||||
|
|
||||||
|
Form(final JSONMapper jsonMapper, final EntityKey entityKey) {
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.objectRoot = this.jsonMapper.createObjectNode();
|
this.objectRoot = this.jsonMapper.createObjectNode();
|
||||||
|
this.entityKey = entityKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityKey entityKey() {
|
||||||
|
return this.entityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -91,6 +101,10 @@ public final class Form implements FormBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void putField(final String name, final Label label, final ImageUpload imageUpload) {
|
||||||
|
this.formFields.put(name, createAccessor(label, imageUpload));
|
||||||
|
}
|
||||||
|
|
||||||
public void putSubForm(final String name, final Form form) {
|
public void putSubForm(final String name, final Form form) {
|
||||||
this.subForms.put(name, form);
|
this.subForms.put(name, form);
|
||||||
}
|
}
|
||||||
|
@ -190,6 +204,13 @@ public final class Form implements FormBinding {
|
||||||
@Override public void setValue(final String value) { singleSelection.select(value); }
|
@Override public void setValue(final String value) { singleSelection.select(value); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload) {
|
||||||
|
return new FormFieldAccessor(label, imageUpload) {
|
||||||
|
@Override public String getValue() { return imageUpload.getImageBase64(); }
|
||||||
|
@Override public void setValue(final String value) { imageUpload.setImageBase64(value); }
|
||||||
|
};
|
||||||
|
}
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
|
|
||||||
public static abstract class FormFieldAccessor {
|
public static abstract class FormFieldAccessor {
|
||||||
|
@ -215,7 +236,6 @@ public final class Form implements FormBinding {
|
||||||
public void setError(final String errorTooltip) {
|
public void setError(final String errorTooltip) {
|
||||||
if (!this.hasError) {
|
if (!this.hasError) {
|
||||||
this.control.setData(RWT.CUSTOM_VARIANT, "error");
|
this.control.setData(RWT.CUSTOM_VARIANT, "error");
|
||||||
//this.control.setBackground(new Color(this.control.getDisplay(), 255, 0, 0, 50));
|
|
||||||
this.control.setToolTipText(errorTooltip);
|
this.control.setToolTipText(errorTooltip);
|
||||||
this.hasError = true;
|
this.hasError = true;
|
||||||
}
|
}
|
||||||
|
@ -224,7 +244,6 @@ public final class Form implements FormBinding {
|
||||||
public void resetError() {
|
public void resetError() {
|
||||||
if (this.hasError) {
|
if (this.hasError) {
|
||||||
this.control.setData(RWT.CUSTOM_VARIANT, null);
|
this.control.setData(RWT.CUSTOM_VARIANT, null);
|
||||||
//this.control.setBackground(new Color(this.control.getDisplay(), 0, 0, 0, 0));
|
|
||||||
this.control.setToolTipText(null);
|
this.control.setToolTipText(null);
|
||||||
this.hasError = false;
|
this.hasError = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.service.page.form;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
|
@ -23,10 +24,13 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.widget.ImageUpload;
|
||||||
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
||||||
|
|
||||||
public class FormBuilder {
|
public class FormBuilder {
|
||||||
|
@ -42,6 +46,7 @@ public class FormBuilder {
|
||||||
private boolean readonly = false;
|
private boolean readonly = false;
|
||||||
|
|
||||||
FormBuilder(
|
FormBuilder(
|
||||||
|
final EntityKey entityKey,
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final WidgetFactory widgetFactory,
|
final WidgetFactory widgetFactory,
|
||||||
final PolyglotPageService polyglotPageService,
|
final PolyglotPageService polyglotPageService,
|
||||||
|
@ -51,7 +56,7 @@ public class FormBuilder {
|
||||||
this.widgetFactory = widgetFactory;
|
this.widgetFactory = widgetFactory;
|
||||||
this.polyglotPageService = polyglotPageService;
|
this.polyglotPageService = polyglotPageService;
|
||||||
this.pageContext = pageContext;
|
this.pageContext = pageContext;
|
||||||
this.form = new Form(jsonMapper);
|
this.form = new Form(jsonMapper, entityKey);
|
||||||
|
|
||||||
this.formParent = new Composite(pageContext.getParent(), SWT.NONE);
|
this.formParent = new Composite(pageContext.getParent(), SWT.NONE);
|
||||||
final GridLayout layout = new GridLayout(rows, true);
|
final GridLayout layout = new GridLayout(rows, true);
|
||||||
|
@ -181,8 +186,48 @@ public class FormBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> FormHandle<T> buildFor(final RestCall<T> post) {
|
public FormBuilder addImageUpload(
|
||||||
return new FormHandle<>(this.pageContext, this.form, post, this.polyglotPageService.getI18nSupport());
|
final String name,
|
||||||
|
final String label,
|
||||||
|
final String value,
|
||||||
|
final int span) {
|
||||||
|
|
||||||
|
return addImageUpload(name, label, value, span, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormBuilder addImageUpload(
|
||||||
|
final String name,
|
||||||
|
final String label,
|
||||||
|
final String value,
|
||||||
|
final int span,
|
||||||
|
final String group) {
|
||||||
|
|
||||||
|
final Label lab = this.widgetFactory.formLabelLocalized(this.formParent, label);
|
||||||
|
final ImageUpload imageUpload = this.widgetFactory.formImageUpload(
|
||||||
|
this.formParent,
|
||||||
|
value,
|
||||||
|
new LocTextKey("sebserver.overall.upload"),
|
||||||
|
span, 1);
|
||||||
|
if (this.readonly) {
|
||||||
|
imageUpload.setReadonly();
|
||||||
|
this.form.putField(name, lab, imageUpload);
|
||||||
|
} else {
|
||||||
|
this.form.putField(name, lab, imageUpload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> FormHandle<T> buildFor(
|
||||||
|
final RestCall<T> post,
|
||||||
|
final Function<T, T> postPostHandle) {
|
||||||
|
|
||||||
|
return new FormHandle<>(
|
||||||
|
this.pageContext,
|
||||||
|
this.form,
|
||||||
|
post,
|
||||||
|
(postPostHandle == null) ? Function.identity() : postPostHandle,
|
||||||
|
this.polyglotPageService.getI18nSupport());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,16 @@
|
||||||
package ch.ethz.seb.sebserver.gui.service.page.form;
|
package ch.ethz.seb.sebserver.gui.service.page.form;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
|
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.form.Form.FormFieldAccessor;
|
import ch.ethz.seb.sebserver.gui.service.page.form.Form.FormFieldAccessor;
|
||||||
|
@ -27,31 +30,38 @@ public class FormHandle<T> {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
|
private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
|
||||||
|
|
||||||
public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "org.sebserver.form.validation.fieldError.";
|
public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError.";
|
||||||
|
|
||||||
private final PageContext pageContext;
|
private final PageContext pageContext;
|
||||||
private final Form form;
|
private final Form form;
|
||||||
private final RestCall<T> post;
|
private final RestCall<T> post;
|
||||||
|
private final Function<T, T> postPostHandle;
|
||||||
private final I18nSupport i18nSupport;
|
private final I18nSupport i18nSupport;
|
||||||
|
|
||||||
FormHandle(
|
FormHandle(
|
||||||
final PageContext pageContext,
|
final PageContext pageContext,
|
||||||
final Form form,
|
final Form form,
|
||||||
final RestCall<T> post,
|
final RestCall<T> post,
|
||||||
|
final Function<T, T> postPostHandle,
|
||||||
final I18nSupport i18nSupport) {
|
final I18nSupport i18nSupport) {
|
||||||
|
|
||||||
this.pageContext = pageContext;
|
this.pageContext = pageContext;
|
||||||
this.form = form;
|
this.form = form;
|
||||||
this.post = post;
|
this.post = post;
|
||||||
|
this.postPostHandle = postPostHandle;
|
||||||
this.i18nSupport = i18nSupport;
|
this.i18nSupport = i18nSupport;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doAPIPost(final ActionDefinition action) {
|
public final Result<T> postChanges(final Action action) {
|
||||||
|
return doAPIPost(action.definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<T> doAPIPost(final ActionDefinition action) {
|
||||||
this.form.process(
|
this.form.process(
|
||||||
name -> true,
|
name -> true,
|
||||||
fieldAccessor -> fieldAccessor.resetError());
|
fieldAccessor -> fieldAccessor.resetError());
|
||||||
|
|
||||||
this.post
|
return this.post
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.withFormBinding(this.form)
|
.withFormBinding(this.form)
|
||||||
.call()
|
.call()
|
||||||
|
@ -71,7 +81,8 @@ public class FormHandle<T> {
|
||||||
log.error("Unexpected error while trying to post form: ", error);
|
log.error("Unexpected error while trying to post form: ", error);
|
||||||
this.pageContext.notifyError(error);
|
this.pageContext.notifyError(error);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.map(this.postPostHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final void showValidationError(
|
private final void showValidationError(
|
||||||
|
|
|
@ -12,12 +12,15 @@ import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@GuiProfile
|
||||||
public class PageFormService {
|
public class PageFormService {
|
||||||
|
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
|
@ -34,8 +37,26 @@ public class PageFormService {
|
||||||
this.polyglotPageService = polyglotPageService;
|
this.polyglotPageService = polyglotPageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormBuilder getBuilder(final PageContext pageContext, final int rows) {
|
public FormBuilder getBuilder(
|
||||||
|
final PageContext pageContext,
|
||||||
|
final int rows) {
|
||||||
|
|
||||||
return new FormBuilder(
|
return new FormBuilder(
|
||||||
|
pageContext.getEntityKey(),
|
||||||
|
this.jsonMapper,
|
||||||
|
this.widgetFactory,
|
||||||
|
this.polyglotPageService,
|
||||||
|
pageContext,
|
||||||
|
rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormBuilder getBuilder(
|
||||||
|
final EntityKey entityKey,
|
||||||
|
final PageContext pageContext,
|
||||||
|
final int rows) {
|
||||||
|
|
||||||
|
return new FormBuilder(
|
||||||
|
entityKey,
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
this.widgetFactory,
|
this.widgetFactory,
|
||||||
this.polyglotPageService,
|
this.polyglotPageService,
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class DefaultLoginPage implements PageDefinition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageContext applyPageContext(final PageContext pageContext) {
|
public PageContext applyPageContext(final PageContext pageContext) {
|
||||||
return pageContext.withAttr(
|
return pageContext.withAttribute(
|
||||||
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME,
|
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME,
|
||||||
SEBLogin.class.getName());
|
SEBLogin.class.getName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class DefaultMainPage implements PageDefinition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageContext applyPageContext(final PageContext pageContext) {
|
public PageContext applyPageContext(final PageContext pageContext) {
|
||||||
return pageContext.withAttr(
|
return pageContext.withAttribute(
|
||||||
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME,
|
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME,
|
||||||
SEBMainPage.class.getName());
|
SEBMainPage.class.getName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.eclipse.swt.layout.RowLayout;
|
||||||
import org.eclipse.swt.widgets.Button;
|
import org.eclipse.swt.widgets.Button;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
import org.eclipse.swt.widgets.Label;
|
import org.eclipse.swt.widgets.Label;
|
||||||
|
import org.eclipse.swt.widgets.MessageBox;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -37,6 +38,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.widget.Message;
|
||||||
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
|
||||||
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
|
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant;
|
||||||
|
|
||||||
|
@ -138,8 +140,15 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
MainPageState.clear();
|
MainPageState.clear();
|
||||||
|
|
||||||
// forward to login page with success message
|
// forward to login page with success message
|
||||||
pageContext.forwardToLoginPage(
|
pageContext.forwardToLoginPage(pageContext);
|
||||||
pageContext.withAttr(AttributeKeys.LGOUT_SUCCESS, "true"));
|
|
||||||
|
// show successful logout message
|
||||||
|
final MessageBox logoutSuccess = new Message(
|
||||||
|
pageContext.getShell(),
|
||||||
|
this.polyglotPageService.getI18nSupport().getText("sebserver.logout"),
|
||||||
|
this.polyglotPageService.getI18nSupport().getText("sebserver.logout.success.message"),
|
||||||
|
SWT.ICON_INFORMATION);
|
||||||
|
logoutSuccess.open(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +193,7 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
logo.setBackgroundImage(new Image(pageContext.getShell().getDisplay(), input));
|
logo.setBackgroundImage(new Image(pageContext.getShell().getDisplay(), input));
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.warn("Get institutional logo failed: ", e);
|
log.warn("Get institutional logo failed: {}", e.getMessage());
|
||||||
logo.setData(RWT.CUSTOM_VARIANT, "bgLogo");
|
logo.setData(RWT.CUSTOM_VARIANT, "bgLogo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessageError;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessageError;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
|
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
|
||||||
|
@ -99,7 +101,7 @@ public class PageContextImpl implements PageContext {
|
||||||
this.composerService,
|
this.composerService,
|
||||||
this.root,
|
this.root,
|
||||||
parent,
|
parent,
|
||||||
this.attributes);
|
new HashMap<>(this.attributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,7 +119,7 @@ public class PageContextImpl implements PageContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageContext withAttr(final String key, final String value) {
|
public PageContext withAttribute(final String key, final String value) {
|
||||||
final Map<String, String> attrs = new HashMap<>();
|
final Map<String, String> attrs = new HashMap<>();
|
||||||
attrs.putAll(this.attributes);
|
attrs.putAll(this.attributes);
|
||||||
attrs.put(key, value);
|
attrs.put(key, value);
|
||||||
|
@ -131,13 +133,15 @@ public class PageContextImpl implements PageContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageContext withSelection(final ActivitySelection selection) {
|
public PageContext withSelection(final ActivitySelection selection, final boolean clearAttributes) {
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, String> attrs = new HashMap<>();
|
final Map<String, String> attrs = new HashMap<>();
|
||||||
attrs.putAll(this.attributes);
|
if (!clearAttributes) {
|
||||||
|
attrs.putAll(this.attributes);
|
||||||
|
}
|
||||||
attrs.putAll(selection.getAttributes());
|
attrs.putAll(selection.getAttributes());
|
||||||
|
|
||||||
return new PageContextImpl(
|
return new PageContextImpl(
|
||||||
|
@ -163,11 +167,59 @@ public class PageContextImpl implements PageContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityKey getEntityKey() {
|
||||||
|
if (hasAttribute(AttributeKeys.ENTITY_ID) && hasAttribute(AttributeKeys.ENTITY_TYPE)) {
|
||||||
|
return new EntityKey(
|
||||||
|
getAttribute(AttributeKeys.ENTITY_ID),
|
||||||
|
EntityType.valueOf(getAttribute(AttributeKeys.ENTITY_TYPE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityKey getParentEntityKey() {
|
||||||
|
if (hasAttribute(AttributeKeys.PARENT_ENTITY_ID) && hasAttribute(AttributeKeys.PARENT_ENTITY_TYPE)) {
|
||||||
|
return new EntityKey(
|
||||||
|
getAttribute(AttributeKeys.PARENT_ENTITY_ID),
|
||||||
|
EntityType.valueOf(getAttribute(AttributeKeys.PARENT_ENTITY_TYPE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageContext withEntityKey(final EntityKey entityKey) {
|
||||||
|
return withAttribute(AttributeKeys.ENTITY_ID, entityKey.modelId)
|
||||||
|
.withAttribute(AttributeKeys.ENTITY_TYPE, entityKey.entityType.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageContext withParentEntityKey(final EntityKey entityKey) {
|
||||||
|
return withAttribute(AttributeKeys.PARENT_ENTITY_ID, entityKey.modelId)
|
||||||
|
.withAttribute(AttributeKeys.PARENT_ENTITY_TYPE, entityKey.entityType.name());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAttribute(final String name) {
|
public boolean hasAttribute(final String name) {
|
||||||
return this.attributes.containsKey(name);
|
return this.attributes.containsKey(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageContext removeAttribute(final String name) {
|
||||||
|
final Map<String, String> attrs = new HashMap<>();
|
||||||
|
attrs.putAll(this.attributes);
|
||||||
|
attrs.remove(name);
|
||||||
|
return new PageContextImpl(
|
||||||
|
this.restService,
|
||||||
|
this.i18nSupport,
|
||||||
|
this.composerService,
|
||||||
|
this.root,
|
||||||
|
this.parent,
|
||||||
|
attrs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends PageEvent> void publishPageEvent(final T event) {
|
public <T extends PageEvent> void publishPageEvent(final T event) {
|
||||||
|
@ -282,8 +334,9 @@ public class PageContextImpl implements PageContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notifyError(final Throwable error) {
|
public <T> T notifyError(final Throwable error) {
|
||||||
notifyError(error.getMessage(), error);
|
notifyError(error.getMessage(), error);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -298,9 +351,7 @@ public class PageContextImpl implements PageContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
MainPageState.clear();
|
MainPageState.clear();
|
||||||
forwardToLoginPage(this.withAttr(
|
forwardToLoginPage(this);
|
||||||
AttributeKeys.AUTHORIZATION_FAILURE,
|
|
||||||
error.getMessage()));
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.springframework.stereotype.Component;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
|
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext;
|
||||||
|
@ -57,14 +56,15 @@ public class SEBLogin implements TemplateComposer {
|
||||||
public void compose(final PageContext pageContext) {
|
public void compose(final PageContext pageContext) {
|
||||||
final Composite parent = pageContext.getParent();
|
final Composite parent = pageContext.getParent();
|
||||||
|
|
||||||
if (pageContext.hasAttribute((AttributeKeys.LGOUT_SUCCESS))) {
|
// if (pageContext.hasAttribute((AttributeKeys.LGOUT_SUCCESS))) {
|
||||||
final MessageBox logoutSuccess = new Message(
|
// final MessageBox logoutSuccess = new Message(
|
||||||
pageContext.getShell(),
|
// pageContext.getShell(),
|
||||||
this.i18nSupport.getText("sebserver.logout"),
|
// this.i18nSupport.getText("sebserver.logout"),
|
||||||
this.i18nSupport.getText("sebserver.logout.success.message"),
|
// this.i18nSupport.getText("sebserver.logout.success.message"),
|
||||||
SWT.ICON_INFORMATION);
|
// SWT.ICON_INFORMATION);
|
||||||
logoutSuccess.open(null);
|
// logoutSuccess.open(null);
|
||||||
}
|
// pageContext = pageContext.removeAttribute(AttributeKeys.LGOUT_SUCCESS);
|
||||||
|
// }
|
||||||
|
|
||||||
final Composite loginGroup = new Composite(parent, SWT.NONE);
|
final Composite loginGroup = new Composite(parent, SWT.NONE);
|
||||||
final GridLayout rowLayout = new GridLayout();
|
final GridLayout rowLayout = new GridLayout();
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gui.service.push;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.Display;
|
||||||
|
|
||||||
|
/** ServerPushContext defines the state of a server push session.
|
||||||
|
*
|
||||||
|
* @author anhefti */
|
||||||
|
public final class ServerPushContext {
|
||||||
|
|
||||||
|
private final Composite anchor;
|
||||||
|
private final Predicate<ServerPushContext> runAgain;
|
||||||
|
|
||||||
|
public ServerPushContext(final Composite anchor) {
|
||||||
|
this(anchor, context -> false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerPushContext(
|
||||||
|
final Composite anchor,
|
||||||
|
final Predicate<ServerPushContext> runAgain) {
|
||||||
|
|
||||||
|
this.anchor = anchor;
|
||||||
|
this.runAgain = runAgain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runAgain() {
|
||||||
|
return this.runAgain.test(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDisposed() {
|
||||||
|
return this.anchor.isDisposed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Display getDisplay() {
|
||||||
|
return this.anchor.getDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Composite getAnchor() {
|
||||||
|
return this.anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void layout() {
|
||||||
|
this.anchor.pack();
|
||||||
|
this.anchor.layout();
|
||||||
|
this.anchor.getParent().layout(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gui.service.push;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.eclipse.rap.rwt.service.ServerPushSession;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Service
|
||||||
|
public class ServerPushService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ServerPushService.class);
|
||||||
|
|
||||||
|
public void runServerPush(
|
||||||
|
final ServerPushContext context,
|
||||||
|
final Consumer<ServerPushContext> business,
|
||||||
|
final Consumer<ServerPushContext> update) {
|
||||||
|
|
||||||
|
final ServerPushSession pushSession = new ServerPushSession();
|
||||||
|
|
||||||
|
pushSession.start();
|
||||||
|
final Thread bgThread = new Thread(() -> {
|
||||||
|
while (!context.isDisposed() && context.runAgain()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.trace("Call business on Server Push Session on: {}", Thread.currentThread().getName());
|
||||||
|
business.accept(context);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Unexpected error while do business for server push service", e);
|
||||||
|
if (context.runAgain()) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.isDisposed()) {
|
||||||
|
|
||||||
|
log.trace("Call update on Server Push Session on: {}", Thread.currentThread().getName());
|
||||||
|
|
||||||
|
context.getDisplay().asyncExec(() -> {
|
||||||
|
try {
|
||||||
|
update.accept(context);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to update on Server Push Session {}. It seems that the UISession is not available anymore. This may source from a connection interruption",
|
||||||
|
Thread.currentThread().getName(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Stop Server Push Session on: {}", Thread.currentThread().getName());
|
||||||
|
try {
|
||||||
|
pushSession.stop();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to stop Server Push Session on: {}. It seems that the UISession is not available anymore. This may source from a connection interruption",
|
||||||
|
Thread.currentThread().getName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("Start new Server Push Session on: {}", bgThread.getName());
|
||||||
|
|
||||||
|
bgThread.setDaemon(true);
|
||||||
|
bgThread.start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,12 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
|
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
|
||||||
public interface FormBinding {
|
public interface FormBinding {
|
||||||
|
|
||||||
|
EntityKey entityKey();
|
||||||
|
|
||||||
String getFormAsJson();
|
String getFormAsJson();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.springframework.web.client.RestClientResponseException;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
|
@ -111,6 +112,7 @@ public abstract class RestCall<T> {
|
||||||
}));
|
}));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Unexpected error-response while webservice API call for: {}", builder, e);
|
log.error("Unexpected error-response while webservice API call for: {}", builder, e);
|
||||||
|
restCallError.errors.add(APIMessage.ErrorMessage.UNEXPECTED.of(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ofError(restCallError);
|
return Result.ofError(restCallError);
|
||||||
|
@ -147,6 +149,7 @@ public abstract class RestCall<T> {
|
||||||
public RestCallBuilder withBody(final Object body) {
|
public RestCallBuilder withBody(final Object body) {
|
||||||
if (body instanceof String) {
|
if (body instanceof String) {
|
||||||
this.body = String.valueOf(body);
|
this.body = String.valueOf(body);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -189,8 +192,8 @@ public abstract class RestCall<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RestCallBuilder withFormBinding(final FormBinding formBinding) {
|
public RestCallBuilder withFormBinding(final FormBinding formBinding) {
|
||||||
// TODO Auto-generated method stub
|
return withURIVariable(API.PATH_VAR_MODEL_ID_NAME, formBinding.entityKey().modelId)
|
||||||
return this;
|
.withBody(formBinding.getFormAsJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
public RestCallBuilder onlyActive(final boolean active) {
|
public RestCallBuilder onlyActive(final boolean active) {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.institution;
|
||||||
|
|
||||||
|
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.model.EntityProcessingReport;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@GuiProfile
|
||||||
|
public class ActivateInstitution extends RestCall<EntityProcessingReport> {
|
||||||
|
|
||||||
|
protected ActivateInstitution() {
|
||||||
|
super(
|
||||||
|
new TypeReference<EntityProcessingReport>() {
|
||||||
|
},
|
||||||
|
HttpMethod.POST,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
|
API.INSTITUTION_ENDPOINT + API.PATH_VAR_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.institution;
|
||||||
|
|
||||||
|
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.model.EntityProcessingReport;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@GuiProfile
|
||||||
|
public class DeactivateInstitution extends RestCall<EntityProcessingReport> {
|
||||||
|
|
||||||
|
protected DeactivateInstitution() {
|
||||||
|
super(
|
||||||
|
new TypeReference<EntityProcessingReport>() {
|
||||||
|
},
|
||||||
|
HttpMethod.POST,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
|
API.INSTITUTION_ENDPOINT + API.PATH_VAR_INACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ public class GetInstitution extends RestCall<Institution> {
|
||||||
},
|
},
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
MediaType.APPLICATION_FORM_URLENCODED,
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
API.INSTITUTION_ENDPOINT);
|
API.INSTITUTION_ENDPOINT + API.PATH_VAR_MODEL_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.institution;
|
||||||
|
|
||||||
|
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.model.institution.Institution;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@GuiProfile
|
||||||
|
public class SaveInstitution extends RestCall<Institution> {
|
||||||
|
|
||||||
|
protected SaveInstitution() {
|
||||||
|
super(
|
||||||
|
new TypeReference<Institution>() {
|
||||||
|
},
|
||||||
|
HttpMethod.PUT,
|
||||||
|
MediaType.APPLICATION_JSON_UTF8,
|
||||||
|
API.INSTITUTION_ENDPOINT + API.PATH_VAR_MODEL_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
|
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
@ -44,6 +46,18 @@ public class CurrentUser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserInfo getOrHandleError(final Function<Throwable, UserInfo> errorHandler) {
|
||||||
|
if (isAvailable()) {
|
||||||
|
return this.authContext
|
||||||
|
.getLoggedInUser()
|
||||||
|
.get(errorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("Current user requested but no user is currently logged in");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
updateContext();
|
updateContext();
|
||||||
return this.authContext != null && this.authContext.isLoggedIn();
|
return this.authContext != null && this.authContext.isLoggedIn();
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
package ch.ethz.seb.sebserver.gui.service.table;
|
package ch.ethz.seb.sebserver.gui.service.table;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
|
@ -25,6 +25,7 @@ import org.eclipse.swt.widgets.TableItem;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
@ -153,16 +154,16 @@ public class EntityTable<ROW extends Entity> extends Composite {
|
||||||
this.sortOrder);
|
this.sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<String> getSelection() {
|
public Set<String> getSelection() {
|
||||||
final TableItem[] selection = this.table.getSelection();
|
final TableItem[] selection = this.table.getSelection();
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Arrays.asList(selection)
|
return Arrays.asList(selection)
|
||||||
.stream()
|
.stream()
|
||||||
.map(this::getRowDataId)
|
.map(this::getRowDataId)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createTableColumns() {
|
private void createTableColumns() {
|
||||||
|
@ -227,7 +228,11 @@ public class EntityTable<ROW extends Entity> extends Composite {
|
||||||
// TODO set an image or HTML with checkbox
|
// TODO set an image or HTML with checkbox
|
||||||
item.setText(index, String.valueOf(value));
|
item.setText(index, String.valueOf(value));
|
||||||
} else {
|
} else {
|
||||||
item.setText(index, String.valueOf(value));
|
if (value != null) {
|
||||||
|
item.setText(index, String.valueOf(value));
|
||||||
|
} else {
|
||||||
|
item.setText(index, Constants.EMPTY_NOTE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* 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.widget;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64InputStream;
|
||||||
|
import org.eclipse.rap.fileupload.FileDetails;
|
||||||
|
import org.eclipse.rap.fileupload.FileUploadHandler;
|
||||||
|
import org.eclipse.rap.fileupload.FileUploadReceiver;
|
||||||
|
import org.eclipse.rap.rwt.RWT;
|
||||||
|
import org.eclipse.rap.rwt.widgets.FileUpload;
|
||||||
|
import org.eclipse.swt.SWT;
|
||||||
|
import org.eclipse.swt.events.SelectionAdapter;
|
||||||
|
import org.eclipse.swt.events.SelectionEvent;
|
||||||
|
import org.eclipse.swt.graphics.Image;
|
||||||
|
import org.eclipse.swt.layout.GridData;
|
||||||
|
import org.eclipse.swt.layout.GridLayout;
|
||||||
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
|
||||||
|
|
||||||
|
public class ImageUpload extends Composite {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 368264811155804533L;
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ImageUpload.class);
|
||||||
|
|
||||||
|
private final ServerPushService serverPushService;
|
||||||
|
|
||||||
|
final Composite imageCanvas;
|
||||||
|
final FileUpload fileUpload;
|
||||||
|
private String imageBase64 = null;
|
||||||
|
private boolean loadNewImage = false;
|
||||||
|
private boolean imageLoaded = false;
|
||||||
|
|
||||||
|
ImageUpload(final Composite parent, final ServerPushService serverPushService) {
|
||||||
|
super(parent, SWT.NONE);
|
||||||
|
super.setLayout(new GridLayout(2, false));
|
||||||
|
|
||||||
|
this.serverPushService = serverPushService;
|
||||||
|
|
||||||
|
this.fileUpload = new FileUpload(this, SWT.NONE);
|
||||||
|
this.fileUpload.setText("Select File");
|
||||||
|
this.fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
|
||||||
|
|
||||||
|
this.imageCanvas = new Composite(this, SWT.NONE);
|
||||||
|
final GridData canvas = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||||
|
this.imageCanvas.setLayoutData(canvas);
|
||||||
|
|
||||||
|
final FileUploadHandler uploadHandler = new FileUploadHandler(new FileUploadReceiver() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(final InputStream stream, final FileDetails details) throws IOException {
|
||||||
|
try {
|
||||||
|
final String contentType = details.getContentType();
|
||||||
|
if (contentType != null && contentType.startsWith("image")) {
|
||||||
|
ImageUpload.this.imageBase64 = Base64.getEncoder().encodeToString(stream.readAllBytes());
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Error while trying to upload image", e);
|
||||||
|
} finally {
|
||||||
|
ImageUpload.this.imageLoaded = true;
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fileUpload.addSelectionListener(new SelectionAdapter() {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -6776734104137568801L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void widgetSelected(final SelectionEvent event) {
|
||||||
|
ImageUpload.this.loadNewImage = true;
|
||||||
|
ImageUpload.this.imageLoaded = false;
|
||||||
|
ImageUpload.this.fileUpload.submit(uploadHandler.getUploadUrl());
|
||||||
|
|
||||||
|
ImageUpload.this.serverPushService.runServerPush(
|
||||||
|
new ServerPushContext(
|
||||||
|
ImageUpload.this,
|
||||||
|
runAgainContext -> {
|
||||||
|
final ImageUpload imageUpload = (ImageUpload) runAgainContext.getAnchor();
|
||||||
|
return imageUpload.loadNewImage && !imageUpload.imageLoaded;
|
||||||
|
}),
|
||||||
|
context -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(200);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context -> {
|
||||||
|
final ImageUpload imageUpload = (ImageUpload) context.getAnchor();
|
||||||
|
if (imageUpload.imageBase64 != null
|
||||||
|
&& imageUpload.loadNewImage
|
||||||
|
&& imageUpload.imageLoaded) {
|
||||||
|
final Base64InputStream input = new Base64InputStream(
|
||||||
|
new ByteArrayInputStream(
|
||||||
|
imageUpload.imageBase64.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
false);
|
||||||
|
|
||||||
|
imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
|
||||||
|
imageUpload.imageCanvas.setBackgroundImage(new Image(context.getDisplay(), input));
|
||||||
|
context.layout();
|
||||||
|
imageUpload.layout();
|
||||||
|
imageUpload.loadNewImage = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageBase64() {
|
||||||
|
return this.imageBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageBase64(final String imageBase64) {
|
||||||
|
if (imageBase64 == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.imageBase64 = imageBase64;
|
||||||
|
final Base64InputStream input = new Base64InputStream(
|
||||||
|
new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), false);
|
||||||
|
this.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
|
||||||
|
this.imageCanvas.setBackgroundImage(new Image(super.getDisplay(), input));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadonly() {
|
||||||
|
this.fileUpload.setVisible(false);
|
||||||
|
this.fileUpload.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.rap.rwt.RWT;
|
import org.eclipse.rap.rwt.RWT;
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
import org.eclipse.swt.graphics.Device;
|
import org.eclipse.swt.graphics.Device;
|
||||||
|
@ -39,6 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
@ -47,6 +49,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
import ch.ethz.seb.sebserver.gui.service.table.TableBuilder;
|
import ch.ethz.seb.sebserver.gui.service.table.TableBuilder;
|
||||||
|
|
||||||
|
@ -78,6 +81,10 @@ public class WidgetFactory {
|
||||||
MAXIMIZE("maximize.png"),
|
MAXIMIZE("maximize.png"),
|
||||||
MINIMIZE("minimize.png"),
|
MINIMIZE("minimize.png"),
|
||||||
MODIFY_ACTION("editAction.png"),
|
MODIFY_ACTION("editAction.png"),
|
||||||
|
CANCEL_ACTION("cancelEditAction.png"),
|
||||||
|
VIEW_ACTION("viewAction.png"),
|
||||||
|
ACTIVATE_ACTION("inactive.png"),
|
||||||
|
DEACTIVATE_ACTION("active.png"),
|
||||||
SAVE_ACTION("saveAction.png"),
|
SAVE_ACTION("saveAction.png"),
|
||||||
NEW_ACTION("newAction.png"),
|
NEW_ACTION("newAction.png"),
|
||||||
DELETE_ACTION("deleteAction.png"),
|
DELETE_ACTION("deleteAction.png"),
|
||||||
|
@ -108,10 +115,15 @@ public class WidgetFactory {
|
||||||
|
|
||||||
private final PolyglotPageService polyglotPageService;
|
private final PolyglotPageService polyglotPageService;
|
||||||
private final I18nSupport i18nSupport;
|
private final I18nSupport i18nSupport;
|
||||||
|
private final ServerPushService serverPushService;
|
||||||
|
|
||||||
|
public WidgetFactory(
|
||||||
|
final PolyglotPageService polyglotPageService,
|
||||||
|
final ServerPushService serverPushService) {
|
||||||
|
|
||||||
public WidgetFactory(final PolyglotPageService polyglotPageService) {
|
|
||||||
this.polyglotPageService = polyglotPageService;
|
this.polyglotPageService = polyglotPageService;
|
||||||
this.i18nSupport = polyglotPageService.getI18nSupport();
|
this.i18nSupport = polyglotPageService.getI18nSupport();
|
||||||
|
this.serverPushService = serverPushService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Button buttonLocalized(final Composite parent, final String locTextKey) {
|
public Button buttonLocalized(final Composite parent, final String locTextKey) {
|
||||||
|
@ -265,7 +277,7 @@ public class WidgetFactory {
|
||||||
|
|
||||||
public Label formValueLabel(final Composite parent, final String value, final int span) {
|
public Label formValueLabel(final Composite parent, final String value, final int span) {
|
||||||
final Label label = new Label(parent, SWT.NONE);
|
final Label label = new Label(parent, SWT.NONE);
|
||||||
label.setText(value);
|
label.setText((StringUtils.isNoneBlank(value)) ? value : Constants.EMPTY_NOTE);
|
||||||
final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, false, span, 1);
|
final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, false, span, 1);
|
||||||
label.setLayoutData(gridData);
|
label.setLayoutData(gridData);
|
||||||
return label;
|
return label;
|
||||||
|
@ -280,7 +292,9 @@ public class WidgetFactory {
|
||||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan);
|
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan);
|
||||||
gridData.heightHint = 15;
|
gridData.heightHint = 15;
|
||||||
textInput.setLayoutData(gridData);
|
textInput.setLayoutData(gridData);
|
||||||
textInput.setText(value);
|
if (value != null) {
|
||||||
|
textInput.setText(value);
|
||||||
|
}
|
||||||
return textInput;
|
return textInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,6 +339,31 @@ public class WidgetFactory {
|
||||||
return combo;
|
return combo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImageUpload formImageUpload(
|
||||||
|
final Composite parent,
|
||||||
|
final String value,
|
||||||
|
final LocTextKey locTextKey,
|
||||||
|
final int hspan, final int vspan) {
|
||||||
|
|
||||||
|
final ImageUpload imageUpload = imageUploadLocalized(parent, locTextKey);
|
||||||
|
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, hspan, vspan);
|
||||||
|
imageUpload.setLayoutData(gridData);
|
||||||
|
imageUpload.setImageBase64(value);
|
||||||
|
return imageUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageUpload imageUploadLocalized(final Composite parent, final LocTextKey locTextKey) {
|
||||||
|
final ImageUpload imageUpload = new ImageUpload(parent, this.serverPushService);
|
||||||
|
injectI18n(imageUpload, locTextKey);
|
||||||
|
return imageUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void injectI18n(final ImageUpload imageUpload, final LocTextKey locTextKey) {
|
||||||
|
final Consumer<ImageUpload> imageUploadFunction = imageUploadFunction(locTextKey, this.i18nSupport);
|
||||||
|
imageUpload.setData(POLYGLOT_WIDGET_FUNCTION_KEY, imageUploadFunction);
|
||||||
|
imageUploadFunction.accept(imageUpload);
|
||||||
|
}
|
||||||
|
|
||||||
public void injectI18n(final Label label, final LocTextKey locTextKey) {
|
public void injectI18n(final Label label, final LocTextKey locTextKey) {
|
||||||
injectI18n(label, locTextKey, null);
|
injectI18n(label, locTextKey, null);
|
||||||
}
|
}
|
||||||
|
@ -409,6 +448,17 @@ public class WidgetFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Consumer<ImageUpload> imageUploadFunction(
|
||||||
|
final LocTextKey locTextKey,
|
||||||
|
final I18nSupport i18nSupport) {
|
||||||
|
|
||||||
|
return imageUpload -> {
|
||||||
|
if (locTextKey != null) {
|
||||||
|
imageUpload.fileUpload.setText(i18nSupport.getText(locTextKey));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static Consumer<Tree> treeFunction(final I18nSupport i18nSupport) {
|
private static Consumer<Tree> treeFunction(final I18nSupport i18nSupport) {
|
||||||
return tree -> updateLocale(tree.getItems(), i18nSupport);
|
return tree -> updateLocale(tree.getItems(), i18nSupport);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
|
|
||||||
/** Defines a authorization grant rule for a specified EntityType.
|
/** Defines a authorization grant rule for a specified EntityType.
|
||||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,13 @@ import javax.annotation.PostConstruct;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.Privilege;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.Privilege.RoleTypeKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.Privilege.RoleTypeKey;
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Service
|
@Service
|
||||||
|
@ -430,6 +432,7 @@ public class AuthorizationGrantServiceImpl implements AuthorizationGrantService
|
||||||
public void create() {
|
public void create() {
|
||||||
final RoleTypeKey roleTypeKey = new RoleTypeKey(this.entityType, this.userRole);
|
final RoleTypeKey roleTypeKey = new RoleTypeKey(this.entityType, this.userRole);
|
||||||
final Privilege roleTypeGrant = new Privilege(
|
final Privilege roleTypeGrant = new Privilege(
|
||||||
|
roleTypeKey,
|
||||||
this.basePrivilege,
|
this.basePrivilege,
|
||||||
this.institutionalPrivilege,
|
this.institutionalPrivilege,
|
||||||
this.ownerPrivilege);
|
this.ownerPrivilege);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
|
|
||||||
public class PermissionDeniedException extends RuntimeException {
|
public class PermissionDeniedException extends RuntimeException {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||||
|
@ -25,7 +26,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamic
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction.Type;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction.Type;
|
||||||
|
|
|
@ -28,8 +28,9 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
@ -41,7 +42,6 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction.Type;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction.Type;
|
||||||
|
@ -103,10 +103,8 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
|
||||||
@RequestParam final MultiValueMap<String, String> allRequestParams) {
|
@RequestParam final MultiValueMap<String, String> allRequestParams) {
|
||||||
|
|
||||||
checkReadPrivilege(institutionId);
|
checkReadPrivilege(institutionId);
|
||||||
final FilterMap filterMap = new FilterMap(allRequestParams);
|
final FilterMap filterMap = new FilterMap(allRequestParams)
|
||||||
allRequestParams.putIfAbsent(
|
.putIfAbsent(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(institutionId));
|
||||||
Entity.FILTER_ATTR_INSTITUTION,
|
|
||||||
Arrays.asList(String.valueOf(institutionId)));
|
|
||||||
|
|
||||||
return this.paginationService.getPage(
|
return this.paginationService.getPage(
|
||||||
pageNumber,
|
pageNumber,
|
||||||
|
@ -133,10 +131,8 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
|
||||||
@RequestParam final MultiValueMap<String, String> allRequestParams) {
|
@RequestParam final MultiValueMap<String, String> allRequestParams) {
|
||||||
|
|
||||||
checkReadPrivilege(institutionId);
|
checkReadPrivilege(institutionId);
|
||||||
final FilterMap filterMap = new FilterMap(allRequestParams);
|
final FilterMap filterMap = new FilterMap(allRequestParams)
|
||||||
allRequestParams.putIfAbsent(
|
.putIfAbsent(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(institutionId));
|
||||||
Entity.FILTER_ATTR_INSTITUTION,
|
|
||||||
Arrays.asList(String.valueOf(institutionId)));
|
|
||||||
|
|
||||||
return getAll(filterMap)
|
return getAll(filterMap)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
|
@ -211,10 +207,10 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
|
||||||
PrivilegeType.WRITE,
|
PrivilegeType.WRITE,
|
||||||
institutionId);
|
institutionId);
|
||||||
|
|
||||||
allRequestParams.putIfAbsent(
|
final POSTMapper postMap = new POSTMapper(allRequestParams)
|
||||||
Domain.ATTR_INSTITUTION_ID,
|
.putIfAbsent(Domain.ATTR_INSTITUTION_ID, String.valueOf(institutionId));
|
||||||
Arrays.asList(String.valueOf(institutionId)));
|
|
||||||
final M requestModel = this.createNew(new POSTMapper(allRequestParams));
|
final M requestModel = this.createNew(postMap);
|
||||||
|
|
||||||
return this.beanValidationService.validateBean(requestModel)
|
return this.beanValidationService.validateBean(requestModel)
|
||||||
.flatMap(this.entityDAO::createNew)
|
.flatMap(this.entityDAO::createNew)
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
|
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
|
@ -46,7 +47,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamic
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
|
@ -30,7 +31,6 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
|
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
|
@ -23,7 +24,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
||||||
|
@ -27,7 +28,6 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,10 @@ sebserver.overall.version=SEB Server Version : {0}
|
||||||
sebserver.overall.imprint=Imprint
|
sebserver.overall.imprint=Imprint
|
||||||
sebserver.overall.about=About
|
sebserver.overall.about=About
|
||||||
|
|
||||||
|
sebserver.overall.message.leave.without.save=You are leaving this page without saved changes!\nThe unsaved changes will be lost.\Are you sure to leave the page?
|
||||||
|
sebserver.overall.upload=Please Select
|
||||||
|
sebserver.overall.action.modify.cancel=Cancel
|
||||||
|
sebserver.overall.action.modify.cancel.confirm=Are you sure to cancel? Modifications will be lost.
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# Login Page
|
# Login Page
|
||||||
|
@ -42,11 +46,24 @@ sebserver.institution.list.title=Institutions
|
||||||
sebserver.institution.list.column.name=Name
|
sebserver.institution.list.column.name=Name
|
||||||
sebserver.institution.list.column.urlSuffix=URL Suffix
|
sebserver.institution.list.column.urlSuffix=URL Suffix
|
||||||
sebserver.institution.list.column.active=Active
|
sebserver.institution.list.column.active=Active
|
||||||
sebserver.institution.action.new=New Institution
|
|
||||||
sebserver.institution.action.modify=Edit Institution
|
|
||||||
sebserver.institution.action.delete=Delete Institution
|
|
||||||
sebserver.institution.info.pleaseSelect=Please Select an Institution from the Table first.
|
|
||||||
|
|
||||||
|
sebserver.institution.action.new=New Institution
|
||||||
|
sebserver.institution.action.view=View Institution
|
||||||
|
sebserver.institution.action.modify=Edit Institution
|
||||||
|
sebserver.institution.action.save=Save Institution
|
||||||
|
sebserver.institution.action.activate=Active
|
||||||
|
sebserver.institution.action.deactivate=Active
|
||||||
|
sebserver.institution.action.delete=Delete Institution
|
||||||
|
|
||||||
|
sebserver.institution.info.pleaseSelect=Please Select an Institution from the Table first.
|
||||||
|
sebserver.institution.form.title=Institution ({0})
|
||||||
|
|
||||||
|
sebserver.institution.form.name=Name
|
||||||
|
sebserver.institution.form.urlSuffix=URL Suffix
|
||||||
|
sebserver.institution.form.logoImage=Logo Image
|
||||||
|
|
||||||
|
sebserver.form.validation.fieldError.name=Name is mandatory and must have a size between 3 and 255 character
|
||||||
|
sebserver.form.validation.fieldError.urlSuffix=URL Suffix must have a size between 3 and 255 character
|
||||||
################################
|
################################
|
||||||
# Form validation
|
# Form validation
|
||||||
################################
|
################################
|
||||||
|
|
|
@ -163,7 +163,7 @@ Text.error {
|
||||||
color: #4a4a4a;
|
color: #4a4a4a;
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-position: left top;
|
background-position: left top;
|
||||||
background-color: #ff0000;
|
background-color: #A8322D;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -258,7 +258,9 @@ Button {
|
||||||
|
|
||||||
/* Push Buttons */
|
/* Push Buttons */
|
||||||
Button[PUSH],
|
Button[PUSH],
|
||||||
Button[PUSH]:default {
|
Button[PUSH]:default,
|
||||||
|
FileUpload,
|
||||||
|
FileUpload:default {
|
||||||
font: bold 12px Arial, Helvetica, sans-serif;
|
font: bold 12px Arial, Helvetica, sans-serif;
|
||||||
background-color: #0069B4;
|
background-color: #0069B4;
|
||||||
background-gradient-color: #0069B4;
|
background-gradient-color: #0069B4;
|
||||||
|
@ -270,14 +272,16 @@ Button[PUSH]:default {
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
Button[PUSH]:pressed {
|
Button[PUSH]:pressed,
|
||||||
|
FileUpload:pressed {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-gradient-color: #444;
|
background-gradient-color: #444;
|
||||||
background-image: gradient( linear, left top, left bottom, from( #444 ), to( #444 ) );
|
background-image: gradient( linear, left top, left bottom, from( #444 ), to( #444 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
Button[PUSH]:hover {
|
Button[PUSH]:hover,
|
||||||
|
FileUpload:hover {
|
||||||
background-color: #82BE1E;
|
background-color: #82BE1E;
|
||||||
background-gradient-color: #82BE1E;
|
background-gradient-color: #82BE1E;
|
||||||
background-image: gradient( linear, left top, left bottom, from( #82BE1E ), to( #82BE1E ) );
|
background-image: gradient( linear, left top, left bottom, from( #82BE1E ), to( #82BE1E ) );
|
||||||
|
@ -285,7 +289,8 @@ Button[PUSH]:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
Button[PUSH]:disabled {
|
Button[PUSH]:disabled,
|
||||||
|
FileUpload:disabled {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid #EAECEE;
|
border: 1px solid #EAECEE;
|
||||||
color: #c0c0c0;
|
color: #c0c0c0;
|
||||||
|
@ -293,7 +298,7 @@ Button[PUSH]:disabled {
|
||||||
background-position: right;
|
background-position: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
Button-FocusIndicator[PUSH] {
|
Button-FocusIndicator[PUSH][BORDER] {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
src/main/resources/static/images/active.png
Normal file
BIN
src/main/resources/static/images/active.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 169 B |
BIN
src/main/resources/static/images/cancelAction.png
Normal file
BIN
src/main/resources/static/images/cancelAction.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 246 B |
BIN
src/main/resources/static/images/cancelEditAction.png
Normal file
BIN
src/main/resources/static/images/cancelEditAction.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
BIN
src/main/resources/static/images/inactive.png
Normal file
BIN
src/main/resources/static/images/inactive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 B |
BIN
src/main/resources/static/images/viewAction.png
Normal file
BIN
src/main/resources/static/images/viewAction.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 211 B |
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.institution;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
|
|
||||||
|
public class InstitutionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws JsonParseException, JsonMappingException, IOException {
|
||||||
|
final String testJson = "{\"id\":\"1\",\"name\":\"ETH Zürichrgerg\",\"urlSuffix\":\"\"}";
|
||||||
|
|
||||||
|
final JSONMapper mapper = new JSONMapper();
|
||||||
|
final Institution inst = mapper.readValue(testJson, Institution.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import org.junit.Test;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
|
|
Loading…
Add table
Reference in a new issue